<!-- build time:Sun Nov 24 2019 22:25:16 GMT+0800 (China Standard Time) --><!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><title>Vue-SSR(Server-Side-Renderer)服务器端渲染指南 - Note?Note!</title><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"><meta name="description" content="介绍什么是服务器端渲染 (SSR)？Vue.js 是构建客户端应用程序的框架。默认情况下，可以在浏览器中输出 Vue 组件，进行生成 DOM 和操作 DOM。然而，也可以将同一个组件渲染为服务器端的 HTML 字符串，将它们直接发送到浏览器，最后将这些静态标记”激活”为客户端上完全可交互的应用程序。服务器渲染的 Vue.js 应用程序也可以被认为是”同构”或”通用”，因为应用程序的大部分代码都可以"><meta name="keywords" content="Vue,Javascript,vue-ssr"><meta property="og:type" content="article"><meta property="og:title" content="Vue-SSR(Server-Side-Renderer)服务器端渲染指南"><meta property="og:url" content="http:&#x2F;&#x2F;www.borgor.cn&#x2F;2019-06-14&#x2F;92f6ccbc.html"><meta property="og:site_name" content="Note?Note!"><meta property="og:description" content="介绍什么是服务器端渲染 (SSR)？Vue.js 是构建客户端应用程序的框架。默认情况下，可以在浏览器中输出 Vue 组件，进行生成 DOM 和操作 DOM。然而，也可以将同一个组件渲染为服务器端的 HTML 字符串，将它们直接发送到浏览器，最后将这些静态标记”激活”为客户端上完全可交互的应用程序。服务器渲染的 Vue.js 应用程序也可以被认为是”同构”或”通用”，因为应用程序的大部分代码都可以"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https:&#x2F;&#x2F;imgs.borgor.cn&#x2F;imgs20190625083301.png"><meta property="og:updated_time" content="2019-11-19T05:53:21.273Z"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https:&#x2F;&#x2F;imgs.borgor.cn&#x2F;imgs20190625083301.png"><link rel="icon" href="/favicon.ico"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.css"><link rel="stylesheet" href="https://cdnjs.loli.net/ajax/libs/font-awesome/5.4.1/css/all.min.css"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu:400,600|Source+Code+Pro"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/atom-one-light.min.css"><style>body>.footer,body>.navbar,body>.section{opacity:0}</style><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/css/lightgallery.min.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.7.0/css/justifiedGallery.min.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.css"><link rel="stylesheet" href="/css/back-to-top.css"><script>var _hmt=_hmt||[];!function(){var e=document.createElement("script");e.src="//hm.baidu.com/hm.js?6cecff6d18ae0c22fd7ac294c80fe7c0";var c=document.getElementsByTagName("script")[0];c.parentNode.insertBefore(e,c)}()</script><link rel="stylesheet" href="/css/progressbar.css"><script src="https://cdnjs.cloudflare.com/ajax/libs/pace/1.0.2/pace.min.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><link rel="stylesheet" href="/css/style.css"></head><body class="is-2-column"><nav class="navbar navbar-main"><div class="container"><div class="navbar-brand is-flex-center"><a class="navbar-item navbar-logo" href="/"><img src="https://imgs.borgor.cn/imgs20190625111723.jpg" alt="Vue-SSR(Server-Side-Renderer)服务器端渲染指南" height="28"></a></div><div class="navbar-menu"><div class="navbar-start"><a class="navbar-item" href="/">首页</a> <a class="navbar-item" href="/archives">归档</a> <a class="navbar-item" href="/categories">分类</a> <a class="navbar-item" href="/tags">标签</a> <a class="navbar-item" href="/readinglist">书单</a></div><div class="navbar-end"><a class="navbar-item search" title="搜索" href="javascript:;" target="_blank" rel="noopener"><i class="fas fa-search"></i></a></div></div></div></nav><section class="section"><div class="container"><div class="columns"><div class="column is-8-tablet is-9-desktop is-9-widescreen has-order-2 column-main"><div class="card"><div class="card-image"><span class="image is-7by1"><img class="thumbnail" src="https://imgs.borgor.cn/imgs20190625083301.png" alt="Vue-SSR(Server-Side-Renderer)服务器端渲染指南"></span></div><div class="card-content article"><div class="level article-meta is-size-7 is-uppercase is-mobile is-overflow-x-auto"><div class="level-left"><div class="level-item"><a href="https://ssr.vuejs.org/zh/" target="_blank">转载于</a></div><time class="level-item has-text-grey" datetime="2019-06-14T14:26:55.000Z">2019-06-14</time><div class="level-item"><a class="has-link-grey -link" href="/categories/Development/">Development</a>&nbsp;/&nbsp;<a class="has-link-grey -link" href="/categories/Development/JavaScript/">JavaScript</a></div><span class="level-item has-text-grey">2 小时 读完 (大约 14134 个字) </span><span class="level-item has-text-grey" id="busuanzi_container_page_pv"><i class="far fa-eye"></i> <span id="busuanzi_value_page_pv">0</span>次访问</span></div></div><h1 class="title is-size-3 is-size-4-mobile has-text-weight-normal">Vue-SSR(Server-Side-Renderer)服务器端渲染指南</h1><div class="content"><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><h2 id="什么是服务器端渲染-SSR-？"><a href="#什么是服务器端渲染-SSR-？" class="headerlink" title="什么是服务器端渲染 (SSR)？"></a>什么是服务器端渲染 (SSR)？</h2><p>Vue.js 是构建客户端应用程序的框架。默认情况下，可以在浏览器中输出 Vue 组件，进行生成 DOM 和操作 DOM。然而，也可以将同一个组件渲染为服务器端的 HTML 字符串，将它们直接发送到浏览器，最后将这些静态标记”激活”为客户端上完全可交互的应用程序。</p><p>服务器渲染的 Vue.js 应用程序也可以被认为是”同构”或”通用”，因为应用程序的大部分代码都可以在<strong>服务器</strong>和<strong>客户端</strong>上运行。</p><a id="more"></a><blockquote><p><strong>注意</strong></p><p>本指南需要最低为如下版本的 Vue，以及以下 library 支持：</p><ul><li>vue &amp; vue-server-renderer 2.3.0+</li><li>vue-router 2.5.0+</li><li>vue-loader 12.0.0+ &amp; vue-style-loader 3.0.0+<br>如果先前已经使用过 Vue 2.2 的服务器端渲染 (SSR)，你应该注意到，推荐的代码结构现在<a href="https://ssr.vuejs.org/zh/guide/structure.html" target="_blank" rel="noopener">略有不同</a>（使用新的 <a href="https://ssr.vuejs.org/zh/api/#runinnewcontext" target="_blank" rel="noopener">runInNewContext</a> 选项，并设置为 <code>false</code>）。现有的应用程序可以继续运行，但建议你迁移到新的推荐规范。</li></ul></blockquote><h2 id="为什么使用服务器端渲染-SSR-？"><a href="#为什么使用服务器端渲染-SSR-？" class="headerlink" title="为什么使用服务器端渲染 (SSR)？"></a>为什么使用服务器端渲染 (SSR)？</h2><p>与传统 SPA (单页应用程序 (Single-Page Application)) 相比，服务器端渲染 (SSR) 的优势主要在于：</p><ul><li><p>更好的 SEO，由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。</p><p>请注意，截至目前，Google 和 Bing 可以很好对同步 JavaScript 应用程序进行索引。在这里，同步是关键。如果你的应用程序初始展示 loading 菊花图，然后通过 Ajax 获取内容，抓取工具并不会等待异步完成后再行抓取页面内容。也就是说，如果 SEO 对你的站点至关重要，而你的页面又是异步获取内容，则你可能需要服务器端渲染(SSR)解决此问题。</p></li><li><p>更快的内容到达时间 (time-to-content)，特别是对于缓慢的网络情况或运行缓慢的设备。无需等待所有的 JavaScript 都完成下载并执行，才显示服务器渲染的标记，所以你的用户将会更快速地看到完整渲染的页面。通常可以产生更好的用户体验，并且对于那些「内容到达时间(time-to-content) 与转化率直接相关」的应用程序而言，服务器端渲染 (SSR) 至关重要。</p></li></ul><p>使用服务器端渲染 (SSR) 时还需要有一些权衡之处：</p><ul><li>开发条件所限。浏览器特定的代码，只能在某些生命周期钩子函数 (lifecycle hook) 中使用；一些外部扩展库 (external library) 可能需要特殊处理，才能在服务器渲染应用程序中运行。</li><li>涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同，服务器渲染应用程序，需要处于 Node.js server 运行环境。</li><li>更多的服务器端负载。在 Node.js 中渲染完整的应用程序，显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源 (CPU-intensive - CPU 密集)，因此如果你预料在高流量环境 (high traffic) 下使用，请准备相应的服务器负载，并明智地采用缓存策略。</li></ul><p>在对你的应用程序使用服务器端渲染 (SSR) 之前，你应该问的第一个问题是，是否真的需要它。这主要取决于内容到达时间 (time-to-content) 对应用程序的重要程度。例如，如果你正在构建一个内部仪表盘，初始加载时的额外几百毫秒并不重要，这种情况下去使用服务器端渲染 (SSR) 将是一个小题大作之举。然而，内容到达时间 (time-to-content) 要求是绝对关键的指标，在这种情况下，服务器端渲染 (SSR) 可以帮助你实现最佳的初始加载性能。</p><h2 id="服务器端渲染-vs-预渲染-SSR-vs-Prerendering"><a href="#服务器端渲染-vs-预渲染-SSR-vs-Prerendering" class="headerlink" title="服务器端渲染 vs 预渲染 (SSR vs Prerendering)"></a>服务器端渲染 vs 预渲染 (SSR vs Prerendering)</h2><p>如果你调研服务器端渲染 (SSR) 只是用来改善少数营销页面（例如 <code>/</code>, <code>/about</code>, <code>/contact</code> 等）的 SEO，那么你可能需要<strong>预渲染</strong>。无需使用 web 服务器实时动态编译 HTML，而是使用预渲染方式，在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单，并可以将你的前端作为一个完全静态的站点。</p><p>如果你使用 webpack，你可以使用 <a href="https://github.com/chrisvfritz/prerender-spa-plugin" target="_blank" rel="noopener">prerender-spa-plugin</a> 轻松地添加预渲染。它已经被 Vue 应用程序广泛测试 - 事实上，<a href="https://github.com/chrisvfritz" target="_blank" rel="noopener">作者</a>是 Vue 核心团队的成员。</p><h2 id="关于此指南"><a href="#关于此指南" class="headerlink" title="关于此指南"></a>关于此指南</h2><p>本指南专注于，使用 Node.js server 的服务器端单页面应用程序渲染。将 Vue 服务器端渲染 (SSR) 与其他后端设置进行混合使用，是后端自身集成 SSR 的话题，我们会在 <strong>后续章节</strong>中进行简要讨论。</p><p>本指南将会非常深入，并且假设你已经熟悉 Vue.js 本身，并且具有 Node.js 和 webpack 的相当不错的应用经验。如果你倾向于使用提供了平滑开箱即用体验的更高层次解决方案，你应该去尝试使用 <a href="https://nuxtjs.org/" target="_blank" rel="noopener">Nuxt.js</a>。它建立在同等的 Vue 技术栈之上，但抽象出很多模板，并提供了一些额外的功能，例如静态站点生成。但是，如果你需要更直接地控制应用程序的结构，Nuxt.js 并不适合这种使用场景。无论如何，阅读本指南将更有助于更好地了解一切如何运行。</p><h1 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h1><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><figure class="highlight bash hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install vue vue-server-renderer --save</span><br></pre></td></tr></table></figure><p>我们将在整个指南中使用 NPM，但你也可以使用 <a href="https://yarnpkg.com/en/" target="_blank" rel="noopener">Yarn</a>。</p><h4 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h4><ul><li>推荐使用 Node.js 版本 6+。</li><li><code>vue-server-renderer</code> 和 <code>vue</code> 必须匹配版本。</li><li><code>vue-server-renderer</code> 依赖一些 Node.js 原生模块，因此只能在 Node.js 中使用。我们可能会提供一个更简单的构建，可以在将来在其他「JavaScript 运行时(runtime)」运行。</li></ul><h2 id="渲染一个-Vue-实例"><a href="#渲染一个-Vue-实例" class="headerlink" title="渲染一个 Vue 实例"></a>渲染一个 Vue 实例</h2><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// 第 1 步：创建一个 Vue 实例</span></span><br><span class="line"><span class="hljs-keyword">const</span> Vue = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Vue(&#123;</span><br><span class="line">  template: <span class="hljs-string">`&lt;div&gt;Hello World&lt;/div&gt;`</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 第 2 步：创建一个 renderer</span></span><br><span class="line"><span class="hljs-keyword">const</span> renderer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer'</span>).createRenderer()</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 第 3 步：将 Vue 实例渲染为 HTML</span></span><br><span class="line">renderer.renderToString(app, (err, html) =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">if</span> (err) <span class="hljs-keyword">throw</span> err</span><br><span class="line">  <span class="hljs-built_in">console</span>.log(html)</span><br><span class="line">  <span class="hljs-comment">// =&gt; &lt;div data-server-rendered="true"&gt;Hello World&lt;/div&gt;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 在 2.5.0+，如果没有传入回调函数，则会返回 Promise：</span></span><br><span class="line">renderer.renderToString(app).then(<span class="hljs-function"><span class="hljs-params">html</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="hljs-built_in">console</span>.log(html)</span><br><span class="line">&#125;).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="hljs-built_in">console</span>.error(err)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="与服务器集成"><a href="#与服务器集成" class="headerlink" title="与服务器集成"></a>与服务器集成</h2><p>在 Node.js 服务器中使用时相当简单直接，例如 <a href="https://expressjs.com/" target="_blank" rel="noopener">Express</a>：</p><figure class="highlight bash hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install express --save</span><br></pre></td></tr></table></figure><hr><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> Vue = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> server = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>)()</span><br><span class="line"><span class="hljs-keyword">const</span> renderer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer'</span>).createRenderer()</span><br><span class="line"></span><br><span class="line">server.get(<span class="hljs-string">'*'</span>, (req, res) =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Vue(&#123;</span><br><span class="line">    data: &#123;</span><br><span class="line">      url: req.url</span><br><span class="line">    &#125;,</span><br><span class="line">    template: <span class="hljs-string">`&lt;div&gt;访问的 URL 是： &#123;&#123; url &#125;&#125;&lt;/div&gt;`</span></span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  renderer.renderToString(app, (err, html) =&gt; &#123;</span><br><span class="line">    <span class="hljs-keyword">if</span> (err) &#123;</span><br><span class="line">      res.status(<span class="hljs-number">500</span>).end(<span class="hljs-string">'Internal Server Error'</span>)</span><br><span class="line">      <span class="hljs-keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    res.end(<span class="hljs-string">`</span></span><br><span class="line"><span class="hljs-string">      &lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="hljs-string">      &lt;html lang="en"&gt;</span></span><br><span class="line"><span class="hljs-string">        &lt;head&gt;&lt;title&gt;Hello&lt;/title&gt;&lt;/head&gt;</span></span><br><span class="line"><span class="hljs-string">        &lt;body&gt;<span class="hljs-subst">$&#123;html&#125;</span>&lt;/body&gt;</span></span><br><span class="line"><span class="hljs-string">      &lt;/html&gt;</span></span><br><span class="line"><span class="hljs-string">    `</span>)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">server.listen(<span class="hljs-number">8080</span>)</span><br></pre></td></tr></table></figure><h2 id="使用一个页面模板"><a href="#使用一个页面模板" class="headerlink" title="使用一个页面模板"></a>使用一个页面模板</h2><p>当你在渲染 Vue 应用程序时，renderer 只从应用程序生成 HTML 标记 (markup)。在这个示例中，我们必须用一个额外的 HTML 页面包裹容器，来包裹生成的 HTML 标记。</p><p>为了简化这些，你可以直接在创建 renderer 时提供一个页面模板。多数时候，我们会将页面模板放在特有的文件中，例如 <code>index.template.html</code>：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Hello<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!--vue-ssr-outlet--&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意 <code>&lt;!--vue-ssr-outlet--&gt;</code> 注释 – 这里将是应用程序 HTML 标记注入的地方。</p><p>然后，我们可以读取和传输文件到 Vue renderer 中：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> renderer = createRenderer(&#123;</span><br><span class="line">  template: <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>).readFileSync(<span class="hljs-string">'./index.template.html'</span>, <span class="hljs-string">'utf-8'</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">renderer.renderToString(app, (err, html) =&gt; &#123;</span><br><span class="line">  <span class="hljs-built_in">console</span>.log(html) <span class="hljs-comment">// html 将是注入应用程序内容的完整页面</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="模板插值"><a href="#模板插值" class="headerlink" title="模板插值"></a>模板插值</h3><p>模板还支持简单插值。给定如下模板：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) --&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>&#123;&#123; title &#125;&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="hljs-comment">&lt;!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) --&gt;</span></span><br><span class="line">    &#123;&#123;&#123; meta &#125;&#125;&#125;</span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!--vue-ssr-outlet--&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>我们可以通过传入一个”渲染上下文对象”，作为 <code>renderToString</code> 函数的第二个参数，来提供插值数据：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> context = &#123;</span><br><span class="line">  title: <span class="hljs-string">'hello'</span>,</span><br><span class="line">  meta: <span class="hljs-string">`</span></span><br><span class="line"><span class="hljs-string">    &lt;meta ...&gt;</span></span><br><span class="line"><span class="hljs-string">    &lt;meta ...&gt;</span></span><br><span class="line"><span class="hljs-string">  `</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">renderer.renderToString(app, context, (err, html) =&gt; &#123;</span><br><span class="line">  <span class="hljs-comment">// 页面 title 将会是 "Hello"</span></span><br><span class="line">  <span class="hljs-comment">// meta 标签也会注入</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>也可以与 Vue 应用程序实例共享 <code>context</code> 对象，允许模板插值中的组件动态地注册数据。</p><p>此外，模板支持一些高级特性，例如：</p><ul><li>在使用 <code>*.vue</code> 组件时，自动注入「关键的 CSS(critical CSS)」；</li><li>在使用 <code>clientManifest</code> 时，自动注入「资源链接(asset links)和资源预加载提示(resource hints)」；</li><li>在嵌入 Vuex 状态进行客户端融合(client-side hydration)时，自动注入以及 XSS 防御。</li></ul><p>在之后的指南中介绍相关概念时，我们将详细讨论这些。</p><h1 id="编写通用代码"><a href="#编写通用代码" class="headerlink" title="编写通用代码"></a>编写通用代码</h1><p>在进一步介绍之前，让我们花点时间来讨论编写”通用”代码时的约束条件 - 即运行在服务器和客户端的代码。由于用例和平台 API 的差异，当运行在不同环境中时，我们的代码将不会完全相同。所以这里我们将会阐述你需要理解的关键事项。</p><h2 id="服务器上的数据响应"><a href="#服务器上的数据响应" class="headerlink" title="服务器上的数据响应"></a>服务器上的数据响应</h2><p>在纯客户端应用程序 (client-only app) 中，每个用户会在他们各自的浏览器中使用新的应用程序实例。对于服务器端渲染，我们也希望如此：每个请求应该都是全新的、独立的应用程序实例，以便不会有交叉请求造成的状态污染 (cross-request state pollution)。</p><p>因为实际的渲染过程需要确定性，所以我们也将在服务器上“预取”数据 (“pre-fetching” data) - 这意味着在我们开始渲染时，我们的应用程序就已经解析完成其状态。也就是说，将数据进行响应式的过程在服务器上是多余的，所以默认情况下禁用。禁用响应式数据，还可以避免将「数据」转换为「响应式对象」的性能开销。</p><h2 id="组件生命周期钩子函数"><a href="#组件生命周期钩子函数" class="headerlink" title="组件生命周期钩子函数"></a>组件生命周期钩子函数</h2><p>由于没有动态更新，所有的生命周期钩子函数中，只有 <code>beforeCreate</code> 和 <code>created</code> 会在服务器端渲染 (SSR) 过程中被调用。这就是说任何其他生命周期钩子函数中的代码（例如 <code>beforeMount</code> 或 <code>mounted</code>），只会在客户端执行。</p><p>此外还需要注意的是，你应该避免在 <code>beforeCreate</code> 和 <code>created</code> 生命周期时产生全局副作用的代码，例如在其中使用 <code>setInterval</code> 设置 timer。在纯客户端 (client-side only) 的代码中，我们可以设置一个 timer，然后在 <code>beforeDestroy</code> 或 <code>destroyed</code> 生命周期时将其销毁。但是，由于在 SSR 期间并不会调用销毁钩子函数，所以 timer 将永远保留下来。为了避免这种情况，请将副作用代码移动到 <code>beforeMount</code> 或 <code>mounted</code> 生命周期中。</p><h2 id="访问特定平台-Platform-Specific-API"><a href="#访问特定平台-Platform-Specific-API" class="headerlink" title="访问特定平台(Platform-Specific) API"></a>访问特定平台(Platform-Specific) API</h2><p>通用代码不可接受特定平台的 API，因此如果你的代码中，直接使用了像 <code>window</code> 或 <code>document</code>，这种仅浏览器可用的全局变量，则会在 Node.js 中执行时抛出错误，反之也是如此。</p><p>对于共享于服务器和客户端，但用于不同平台 API 的任务(task)，建议将平台特定实现包含在通用 API 中，或者使用为你执行此操作的 library。例如，<a href="https://github.com/axios/axios" target="_blank" rel="noopener">axios</a> 是一个 HTTP 客户端，可以向服务器和客户端都暴露相同的 API。</p><p>对于仅浏览器可用的 API，通常方式是，在「纯客户端 (client-only)」的生命周期钩子函数中惰性访问 (lazily access) 它们。</p><p>请注意，考虑到如果第三方 library 不是以上面的通用用法编写，则将其集成到服务器渲染的应用程序中，可能会很棘手。你<em>可能</em>要通过模拟 (mock) 一些全局变量来使其正常运行，但这只是 hack 的做法，并且可能会干扰到其他 library 的环境检测代码。</p><h2 id="自定义指令"><a href="#自定义指令" class="headerlink" title="自定义指令"></a>自定义指令</h2><p>大多数自定义指令直接操作 DOM，因此会在服务器端渲染 (SSR) 过程中导致错误。有两种方法可以解决这个问题：</p><ol><li>推荐使用组件作为抽象机制，并运行在「虚拟 DOM 层级(Virtual-DOM level)」（例如，使用渲染函数(render function)）。</li><li>如果你有一个自定义指令，但是不是很容易替换为组件，则可以在创建服务器 renderer 时，使用 <a href="https://ssr.vuejs.org/zh/api/#directives" target="_blank" rel="noopener"><code>directives</code></a> 选项所提供”服务器端版本(server-side version)”。</li></ol><h1 id="源码结构"><a href="#源码结构" class="headerlink" title="源码结构"></a>源码结构</h1><h2 id="避免状态单例"><a href="#避免状态单例" class="headerlink" title="避免状态单例"></a>避免状态单例</h2><p>当编写纯客户端 (client-only) 代码时，我们习惯于每次在新的上下文中对代码进行取值。但是，Node.js 服务器是一个长期运行的进程。当我们的代码进入该进程时，它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象，它将在每个传入的请求之间共享。</p><p>如基本示例所示，我们<strong>为每个请求创建一个新的根 Vue 实例</strong>。这与每个用户在自己的浏览器中使用新应用程序的实例类似。如果我们在多个请求之间使用一个共享的实例，很容易导致交叉请求状态污染 (cross-request state pollution)。</p><p>因此，我们不应该直接创建一个应用程序实例，而是应该暴露一个可以重复执行的工厂函数，为每个请求创建新的应用程序实例：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// app.js</span></span><br><span class="line"><span class="hljs-keyword">const</span> Vue = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createApp</span> (<span class="hljs-params">context</span>) </span>&#123;</span><br><span class="line">  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Vue(&#123;</span><br><span class="line">    data: &#123;</span><br><span class="line">      url: context.url</span><br><span class="line">    &#125;,</span><br><span class="line">    template: <span class="hljs-string">`&lt;div&gt;访问的 URL 是： &#123;&#123; url &#125;&#125;&lt;/div&gt;`</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>并且我们的服务器代码现在变为：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// server.js</span></span><br><span class="line"><span class="hljs-keyword">const</span> createApp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./app'</span>)</span><br><span class="line"></span><br><span class="line">server.get(<span class="hljs-string">'*'</span>, (req, res) =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">const</span> context = &#123; <span class="hljs-attr">url</span>: req.url &#125;</span><br><span class="line">  <span class="hljs-keyword">const</span> app = createApp(context)</span><br><span class="line"></span><br><span class="line">  renderer.renderToString(app, (err, html) =&gt; &#123;</span><br><span class="line">    <span class="hljs-comment">// 处理错误……</span></span><br><span class="line">    res.end(html)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>同样的规则也适用于 router、store 和 event bus 实例。你不应该直接从模块导出并将其导入到应用程序中，而是需要在 <code>createApp</code> 中创建一个新的实例，并从根 Vue 实例注入。</p><blockquote><p>在使用带有 <code>{ runInNewContext: true }</code> 的 bundle renderer 时，可以消除此约束，但是由于需要为每个请求创建一个新的 vm 上下文，因此伴随有一些显著性能开销。</p></blockquote><h2 id="介绍构建步骤"><a href="#介绍构建步骤" class="headerlink" title="介绍构建步骤"></a>介绍构建步骤</h2><p>到目前为止，我们还没有讨论过如何将相同的 Vue 应用程序提供给客户端。为了做到这一点，我们需要使用 webpack 来打包我们的 Vue 应用程序。事实上，我们可能需要在服务器上使用 webpack 打包 Vue 应用程序，因为：</p><ul><li>通常 Vue 应用程序是由 webpack 和 <code>vue-loader</code> 构建，并且许多 webpack 特定功能不能直接在 Node.js 中运行（例如通过 <code>file-loader</code> 导入文件，通过 <code>css-loader</code> 导入 CSS）。</li><li>尽管 Node.js 最新版本能够完全支持 ES2015 特性，我们还是需要转译客户端代码以适应老版浏览器。这也会涉及到构建步骤。</li></ul><p>所以基本看法是，对于客户端应用程序和服务器应用程序，我们都要使用 webpack 打包 - 服务器需要「服务器 bundle」然后用于服务器端渲染(SSR)，而「客户端 bundle」会发送给浏览器，用于混合静态标记。</p><p><img src="https://img-bed-1251517122.file.myqcloud.com/imgs20190620155202.png" alt></p><p>我们将在后面的章节讨论规划结构的细节 - 现在，先假设我们已经将构建过程的规划都弄清楚了，我们可以在启用 webpack 的情况下编写我们的 Vue 应用程序代码。</p><h2 id="使用-webpack-的源码结构"><a href="#使用-webpack-的源码结构" class="headerlink" title="使用 webpack 的源码结构"></a>使用 webpack 的源码结构</h2><p>现在我们正在使用 webpack 来处理服务器和客户端的应用程序，大部分源码可以使用通用方式编写，可以使用 webpack 支持的所有功能。同时，在编写通用代码时，有一些<a href="https://ssr.vuejs.org/zh/guide/universal.html" target="_blank" rel="noopener">事项</a>要牢记在心。</p><p>一个基本项目可能像是这样：</p><figure class="highlight bash hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">src</span><br><span class="line">├── components</span><br><span class="line">│   ├── Foo.vue</span><br><span class="line">│   ├── Bar.vue</span><br><span class="line">│   └── Baz.vue</span><br><span class="line">├── App.vue</span><br><span class="line">├── app.js <span class="hljs-comment"># 通用 entry(universal entry)</span></span><br><span class="line">├── entry-client.js <span class="hljs-comment"># 仅运行于浏览器</span></span><br><span class="line">└── entry-server.js <span class="hljs-comment"># 仅运行于服务器</span></span><br></pre></td></tr></table></figure><h3 id="app-js"><a href="#app-js" class="headerlink" title="app.js"></a><code>app.js</code></h3><p><code>app.js</code> 是我们应用程序的「通用 entry」。在纯客户端应用程序中，我们将在此文件中创建根 Vue 实例，并直接挂载到 DOM。但是，对于服务器端渲染(SSR)，责任转移到纯客户端 entry 文件。<code>app.js</code>简单地使用 export 导出一个 <code>createApp</code> 函数：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App.vue'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 导出一个工厂函数，用于创建新的</span></span><br><span class="line"><span class="hljs-comment">// 应用程序、router 和 store 实例</span></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createApp</span> (<span class="hljs-params"></span>) </span>&#123;</span><br><span class="line">  <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Vue(&#123;</span><br><span class="line">    <span class="hljs-comment">// 根实例简单的渲染应用程序组件。</span></span><br><span class="line">    render: <span class="hljs-function"><span class="hljs-params">h</span> =&gt;</span> h(App)</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="hljs-keyword">return</span> &#123; app &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="entry-client-js"><a href="#entry-client-js" class="headerlink" title="entry-client.js:"></a><code>entry-client.js</code>:</h3><p>客户端 entry 只需创建应用程序，并且将其挂载到 DOM 中：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">import</span> &#123; createApp &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 客户端特定引导逻辑……</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> &#123; app &#125; = createApp()</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 这里假定 App.vue 模板中根元素具有 `id="app"`</span></span><br><span class="line">app.$mount(<span class="hljs-string">'#app'</span>)</span><br></pre></td></tr></table></figure><h3 id="entry-server-js"><a href="#entry-server-js" class="headerlink" title="entry-server.js:"></a><code>entry-server.js</code>:</h3><p>服务器 entry 使用 default export 导出函数，并在每次渲染中重复调用此函数。此时，除了创建和返回应用程序实例之外，它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配 (server-side route matching) 和数据预取逻辑 (data pre-fetching logic)。</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">import</span> &#123; createApp &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> context =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">const</span> &#123; app &#125; = createApp()</span><br><span class="line">  <span class="hljs-keyword">return</span> app</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="路由和代码分割"><a href="#路由和代码分割" class="headerlink" title="路由和代码分割"></a>路由和代码分割</h1><h2 id="使用-vue-router-的路由"><a href="#使用-vue-router-的路由" class="headerlink" title="使用 vue-router 的路由"></a>使用 <code>vue-router</code> 的路由</h2><p>你可能已经注意到，我们的服务器代码使用了一个 <code>*</code> 处理程序，它接受任意 URL。这允许我们将访问的 URL 传递到我们的 Vue 应用程序中，然后对客户端和服务器复用相同的路由配置！</p><p>为此，建议使用官方提供的 <code>vue-router</code>。我们首先创建一个文件，在其中创建 router。注意，类似于 <code>createApp</code>，我们也需要给每个请求一个新的 router 实例，所以文件导出一个 <code>createRouter</code> 函数：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// router.js</span></span><br><span class="line"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> Router <span class="hljs-keyword">from</span> <span class="hljs-string">'vue-router'</span></span><br><span class="line"></span><br><span class="line">Vue.use(Router)</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createRouter</span> (<span class="hljs-params"></span>) </span>&#123;</span><br><span class="line">  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Router(&#123;</span><br><span class="line">    mode: <span class="hljs-string">'history'</span>,</span><br><span class="line">    routes: [</span><br><span class="line">      <span class="hljs-comment">// ...</span></span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后更新 <code>app.js</code>:</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// app.js</span></span><br><span class="line"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App.vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; createRouter &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./router'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createApp</span> (<span class="hljs-params"></span>) </span>&#123;</span><br><span class="line">  <span class="hljs-comment">// 创建 router 实例</span></span><br><span class="line">  <span class="hljs-keyword">const</span> router = createRouter()</span><br><span class="line"></span><br><span class="line">  <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Vue(&#123;</span><br><span class="line">    <span class="hljs-comment">// 注入 router 到根 Vue 实例</span></span><br><span class="line">    router,</span><br><span class="line">    render: <span class="hljs-function"><span class="hljs-params">h</span> =&gt;</span> h(App)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 返回 app 和 router</span></span><br><span class="line">  <span class="hljs-keyword">return</span> &#123; app, router &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在我们需要在 <code>entry-server.js</code> 中实现服务器端路由逻辑 (server-side routing logic)：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// entry-server.js</span></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; createApp &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> context =&gt; &#123;</span><br><span class="line">  <span class="hljs-comment">// 因为有可能会是异步路由钩子函数或组件，所以我们将返回一个 Promise，</span></span><br><span class="line">    <span class="hljs-comment">// 以便服务器能够等待所有的内容在渲染前，</span></span><br><span class="line">    <span class="hljs-comment">// 就已经准备就绪。</span></span><br><span class="line">  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> &#123; app, router &#125; = createApp()</span><br><span class="line"></span><br><span class="line">    <span class="hljs-comment">// 设置服务器端 router 的位置</span></span><br><span class="line">    router.push(context.url)</span><br><span class="line"></span><br><span class="line">    <span class="hljs-comment">// 等到 router 将可能的异步组件和钩子函数解析完</span></span><br><span class="line">    router.onReady(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="hljs-keyword">const</span> matchedComponents = router.getMatchedComponents()</span><br><span class="line">      <span class="hljs-comment">// 匹配不到的路由，执行 reject 函数，并返回 404</span></span><br><span class="line">      <span class="hljs-keyword">if</span> (!matchedComponents.length) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> reject(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">404</span> &#125;)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="hljs-comment">// Promise 应该 resolve 应用程序实例，以便它可以渲染</span></span><br><span class="line">      resolve(app)</span><br><span class="line">    &#125;, reject)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设服务器 bundle 已经完成构建（请再次忽略现在的构建设置），服务器用法看起来如下：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// server.js</span></span><br><span class="line"><span class="hljs-keyword">const</span> createApp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'/path/to/built-server-bundle.js'</span>)</span><br><span class="line"></span><br><span class="line">server.get(<span class="hljs-string">'*'</span>, (req, res) =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">const</span> context = &#123; <span class="hljs-attr">url</span>: req.url &#125;</span><br><span class="line"></span><br><span class="line">  createApp(context).then(<span class="hljs-function"><span class="hljs-params">app</span> =&gt;</span> &#123;</span><br><span class="line">    renderer.renderToString(app, (err, html) =&gt; &#123;</span><br><span class="line">      <span class="hljs-keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="hljs-keyword">if</span> (err.code === <span class="hljs-number">404</span>) &#123;</span><br><span class="line">          res.status(<span class="hljs-number">404</span>).end(<span class="hljs-string">'Page not found'</span>)</span><br><span class="line">        &#125; <span class="hljs-keyword">else</span> &#123;</span><br><span class="line">          res.status(<span class="hljs-number">500</span>).end(<span class="hljs-string">'Internal Server Error'</span>)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="hljs-keyword">else</span> &#123;</span><br><span class="line">        res.end(html)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="代码分割"><a href="#代码分割" class="headerlink" title="代码分割"></a>代码分割</h2><p>应用程序的代码分割或惰性加载，有助于减少浏览器在初始渲染中下载的资源体积，可以极大地改善大体积 bundle 的可交互时间(TTI - time-to-interactive)。这里的关键在于，对初始首屏而言，”只加载所需”。</p><p>Vue 提供异步组件作为第一类的概念，将其与 <a href="https://webpack.js.org/guides/code-splitting-async/" target="_blank" rel="noopener">webpack 2 所支持的使用动态导入作为代码分割点</a>相结合，你需要做的是：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// 这里进行修改……</span></span><br><span class="line"><span class="hljs-keyword">import</span> Foo <span class="hljs-keyword">from</span> <span class="hljs-string">'./Foo.vue'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 改为这样：</span></span><br><span class="line"><span class="hljs-keyword">const</span> Foo = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./Foo.vue'</span>)</span><br></pre></td></tr></table></figure><p>在 Vue 2.5 以下的版本中，服务端渲染时异步组件只能用在路由组件上。然而在 2.5+ 的版本中，得益于核心算法的升级，异步组件现在可以在应用中的任何地方使用。</p><p>需要注意的是，你仍然需要在挂载 app 之前调用 <code>router.onReady</code>，因为路由器必须要提前解析路由配置中的异步组件，才能正确地调用组件中可能存在的路由钩子。这一步我们已经在我们的服务器入口 (server entry) 中实现过了，现在我们只需要更新客户端入口 (client entry)：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// entry-client.js</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; createApp &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> &#123; app, router &#125; = createApp()</span><br><span class="line"></span><br><span class="line">router.onReady(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;</span><br><span class="line">  app.$mount(<span class="hljs-string">'#app'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>异步路由组件的路由配置示例：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// router.js</span></span><br><span class="line"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> Router <span class="hljs-keyword">from</span> <span class="hljs-string">'vue-router'</span></span><br><span class="line"></span><br><span class="line">Vue.use(Router)</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createRouter</span> (<span class="hljs-params"></span>) </span>&#123;</span><br><span class="line">  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Router(&#123;</span><br><span class="line">    mode: <span class="hljs-string">'history'</span>,</span><br><span class="line">    routes: [</span><br><span class="line">      &#123; <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>, <span class="hljs-attr">component</span>: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/Home.vue'</span>) &#125;,</span><br><span class="line">      &#123; <span class="hljs-attr">path</span>: <span class="hljs-string">'/item/:id'</span>, <span class="hljs-attr">component</span>: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/Item.vue'</span>) &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="数据预取和状态"><a href="#数据预取和状态" class="headerlink" title="数据预取和状态"></a>数据预取和状态</h1><h2 id="数据预取存储容器-Data-Store"><a href="#数据预取存储容器-Data-Store" class="headerlink" title="数据预取存储容器 (Data Store)"></a>数据预取存储容器 (Data Store)</h2><p>在服务器端渲染(SSR)期间，我们本质上是在渲染我们应用程序的”快照”，所以如果应用程序依赖于一些异步数据，<strong>那么在开始渲染过程之前，需要先预取和解析好这些数据</strong>。</p><p>另一个需要关注的问题是在客户端，在挂载 (mount) 到客户端应用程序之前，需要获取到与服务器端应用程序完全相同的数据 - 否则，客户端应用程序会因为使用与服务器端应用程序不同的状态，然后导致混合失败。</p><p>为了解决这个问题，获取的数据需要位于视图组件之外，即放置在专门的数据预取存储容器(data store)或”状态容器(state container)）”中。首先，在服务器端，我们可以在渲染之前预取数据，并将数据填充到 store 中。此外，我们将在 HTML 中序列化(serialize)和内联预置(inline)状态。这样，在挂载(mount)到客户端应用程序之前，可以直接从 store 获取到内联预置(inline)状态。</p><p>为此，我们将使用官方状态管理库 <a href="https://github.com/vuejs/vuex/" target="_blank" rel="noopener">Vuex</a>。我们先创建一个 <code>store.js</code> 文件，里面会模拟一些根据 id 获取 item 的逻辑：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// store.js</span></span><br><span class="line"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> Vuex <span class="hljs-keyword">from</span> <span class="hljs-string">'vuex'</span></span><br><span class="line"></span><br><span class="line">Vue.use(Vuex)</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 假定我们有一个可以返回 Promise 的</span></span><br><span class="line"><span class="hljs-comment">// 通用 API（请忽略此 API 具体实现细节）</span></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; fetchItem &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./api'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createStore</span> (<span class="hljs-params"></span>) </span>&#123;</span><br><span class="line">  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Vuex.Store(&#123;</span><br><span class="line">    state: &#123;</span><br><span class="line">      items: &#123;&#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    actions: &#123;</span><br><span class="line">      fetchItem (&#123; commit &#125;, id) &#123;</span><br><span class="line">        <span class="hljs-comment">// `store.dispatch()` 会返回 Promise，</span></span><br><span class="line">        <span class="hljs-comment">// 以便我们能够知道数据在何时更新</span></span><br><span class="line">        <span class="hljs-keyword">return</span> fetchItem(id).then(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> &#123;</span><br><span class="line">          commit(<span class="hljs-string">'setItem'</span>, &#123; id, item &#125;)</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    mutations: &#123;</span><br><span class="line">      setItem (state, &#123; id, item &#125;) &#123;</span><br><span class="line">        Vue.set(state.items, id, item)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后修改 <code>app.js</code>：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// app.js</span></span><br><span class="line"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App.vue'</span></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; createRouter &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./router'</span></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; createStore &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./store'</span></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; sync &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'vuex-router-sync'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createApp</span> (<span class="hljs-params"></span>) </span>&#123;</span><br><span class="line">  <span class="hljs-comment">// 创建 router 和 store 实例</span></span><br><span class="line">  <span class="hljs-keyword">const</span> router = createRouter()</span><br><span class="line">  <span class="hljs-keyword">const</span> store = createStore()</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 同步路由状态(route state)到 store</span></span><br><span class="line">  sync(store, router)</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 创建应用程序实例，将 router 和 store 注入</span></span><br><span class="line">  <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Vue(&#123;</span><br><span class="line">    router,</span><br><span class="line">    store,</span><br><span class="line">    render: <span class="hljs-function"><span class="hljs-params">h</span> =&gt;</span> h(App)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 暴露 app, router 和 store。</span></span><br><span class="line">  <span class="hljs-keyword">return</span> &#123; app, router, store &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="带有逻辑配置的组件-Logic-Collocation-with-Components"><a href="#带有逻辑配置的组件-Logic-Collocation-with-Components" class="headerlink" title="带有逻辑配置的组件 (Logic Collocation with Components)"></a>带有逻辑配置的组件 (Logic Collocation with Components)</h2><p>那么，我们在哪里放置「dispatch 数据预取 action」的代码？</p><p>我们需要通过访问路由，来决定获取哪部分数据 - 这也决定了哪些组件需要渲染。事实上，给定路由所需的数据，也是在该路由上渲染组件时所需的数据。所以在路由组件中放置数据预取逻辑，是很自然的事情。</p><p>我们将在路由组件上暴露出一个自定义静态函数 <code>asyncData</code>。注意，由于此函数会在组件实例化之前调用，所以它无法访问 <code>this</code>。需要将 store 和路由信息作为参数传递进去：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">&lt;!-- Item.vue --&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>&#123;&#123; item.title &#125;&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span></span><br><span class="line"><span class="hljs-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> &#123;</span></span><br><span class="line">  asyncData (&#123; store, route &#125;) &#123;</span><br><span class="line"><span class="hljs-actionscript">    <span class="hljs-comment">// 触发 action 后，会返回 Promise</span></span></span><br><span class="line"><span class="hljs-actionscript">    <span class="hljs-keyword">return</span> store.dispatch(<span class="hljs-string">'fetchItem'</span>, route.params.id)</span></span><br><span class="line">  &#125;,</span><br><span class="line">  computed: &#123;</span><br><span class="line"><span class="hljs-actionscript">    <span class="hljs-comment">// 从 store 的 state 对象中的获取 item。</span></span></span><br><span class="line">    item () &#123;</span><br><span class="line"><span class="hljs-actionscript">      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.$store.state.items[<span class="hljs-keyword">this</span>.$route.params.id]</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="服务器端数据预取-Server-Data-Fetching"><a href="#服务器端数据预取-Server-Data-Fetching" class="headerlink" title="服务器端数据预取 (Server Data Fetching)"></a>服务器端数据预取 (Server Data Fetching)</h2><p>在 <code>entry-server.js</code> 中，我们可以通过路由获得与 <code>router.getMatchedComponents()</code> 相匹配的组件，如果组件暴露出 <code>asyncData</code>，我们就调用这个方法。然后我们需要将解析完成的状态，附加到渲染上下文(render context)中。</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// entry-server.js</span></span><br><span class="line"><span class="hljs-keyword">import</span> &#123; createApp &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> context =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> &#123; app, router, store &#125; = createApp()</span><br><span class="line"></span><br><span class="line">    router.push(context.url)</span><br><span class="line"></span><br><span class="line">    router.onReady(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="hljs-keyword">const</span> matchedComponents = router.getMatchedComponents()</span><br><span class="line">      <span class="hljs-keyword">if</span> (!matchedComponents.length) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> reject(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">404</span> &#125;)</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="hljs-comment">// 对所有匹配的路由组件调用 `asyncData()`</span></span><br><span class="line">      <span class="hljs-built_in">Promise</span>.all(matchedComponents.map(<span class="hljs-function"><span class="hljs-params">Component</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="hljs-keyword">if</span> (Component.asyncData) &#123;</span><br><span class="line">          <span class="hljs-keyword">return</span> Component.asyncData(&#123;</span><br><span class="line">            store,</span><br><span class="line">            route: router.currentRoute</span><br><span class="line">          &#125;)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)).then(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="hljs-comment">// 在所有预取钩子(preFetch hook) resolve 后，</span></span><br><span class="line">        <span class="hljs-comment">// 我们的 store 现在已经填充入渲染应用程序所需的状态。</span></span><br><span class="line">        <span class="hljs-comment">// 当我们将状态附加到上下文，</span></span><br><span class="line">        <span class="hljs-comment">// 并且 `template` 选项用于 renderer 时，</span></span><br><span class="line">        <span class="hljs-comment">// 状态将自动序列化为 `window.__INITIAL_STATE__`，并注入 HTML。</span></span><br><span class="line">        context.state = store.state</span><br><span class="line"></span><br><span class="line">        resolve(app)</span><br><span class="line">      &#125;).catch(reject)</span><br><span class="line">    &#125;, reject)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当使用 <code>template</code> 时，<code>context.state</code> 将作为 <code>window.__INITIAL_STATE__</code> 状态，自动嵌入到最终的 HTML 中。而在客户端，在挂载到应用程序之前，store 就应该获取到状态：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// entry-client.js</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> &#123; app, router, store &#125; = createApp()</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.__INITIAL_STATE__) &#123;</span><br><span class="line">  store.replaceState(<span class="hljs-built_in">window</span>.__INITIAL_STATE__)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="客户端数据预取-Client-Data-Fetching"><a href="#客户端数据预取-Client-Data-Fetching" class="headerlink" title="客户端数据预取 (Client Data Fetching)"></a>客户端数据预取 (Client Data Fetching)</h2><p>在客户端，处理数据预取有两种不同方式：</p><ol><li><strong>在路由导航之前解析数据：</strong></li></ol><p>使用此策略，应用程序会等待视图所需数据全部解析之后，再传入数据并处理当前视图。好处在于，可以直接在数据准备就绪时，传入视图渲染完整内容，但是如果数据预取需要很长时间，用户在当前视图会感受到”明显卡顿”。因此，如果使用此策略，建议提供一个数据加载指示器 (data loading indicator)。</p><p>我们可以通过检查匹配的组件，并在全局路由钩子函数中执行 <code>asyncData</code> 函数，来在客户端实现此策略。注意，在初始路由准备就绪之后，我们应该注册此钩子，这样我们就不必再次获取服务器提取的数据。</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// entry-client.js</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// ...忽略无关代码</span></span><br><span class="line"></span><br><span class="line">router.onReady(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="hljs-comment">// 添加路由钩子函数，用于处理 asyncData.</span></span><br><span class="line">  <span class="hljs-comment">// 在初始路由 resolve 后执行，</span></span><br><span class="line">  <span class="hljs-comment">// 以便我们不会二次预取(double-fetch)已有的数据。</span></span><br><span class="line">  <span class="hljs-comment">// 使用 `router.beforeResolve()`，以便确保所有异步组件都 resolve。</span></span><br><span class="line">  router.beforeResolve(<span class="hljs-function">(<span class="hljs-params">to, <span class="hljs-keyword">from</span>, next</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> matched = router.getMatchedComponents(to)</span><br><span class="line">    <span class="hljs-keyword">const</span> prevMatched = router.getMatchedComponents(<span class="hljs-keyword">from</span>)</span><br><span class="line"></span><br><span class="line">    <span class="hljs-comment">// 我们只关心非预渲染的组件</span></span><br><span class="line">    <span class="hljs-comment">// 所以我们对比它们，找出两个匹配列表的差异组件</span></span><br><span class="line">    <span class="hljs-keyword">let</span> diffed = <span class="hljs-literal">false</span></span><br><span class="line">    <span class="hljs-keyword">const</span> activated = matched.filter(<span class="hljs-function">(<span class="hljs-params">c, i</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="hljs-keyword">return</span> diffed || (diffed = (prevMatched[i] !== c))</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    <span class="hljs-keyword">if</span> (!activated.length) &#123;</span><br><span class="line">      <span class="hljs-keyword">return</span> next()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="hljs-comment">// 这里如果有加载指示器 (loading indicator)，就触发</span></span><br><span class="line"></span><br><span class="line">    <span class="hljs-built_in">Promise</span>.all(activated.map(<span class="hljs-function"><span class="hljs-params">c</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="hljs-keyword">if</span> (c.asyncData) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> c.asyncData(&#123; store, <span class="hljs-attr">route</span>: to &#125;)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)).then(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">      <span class="hljs-comment">// 停止加载指示器(loading indicator)</span></span><br><span class="line"></span><br><span class="line">      next()</span><br><span class="line">    &#125;).catch(next)</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  app.$mount(<span class="hljs-string">'#app'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><ol><li><strong>匹配要渲染的视图后，再获取数据：</strong></li></ol><p>此策略将客户端数据预取逻辑，放在视图组件的 <code>beforeMount</code> 函数中。当路由导航被触发时，可以立即切换视图，因此应用程序具有更快的响应速度。然而，传入视图在渲染时不会有完整的可用数据。因此，对于使用此策略的每个视图组件，都需要具有条件加载状态。</p><p>这可以通过纯客户端 (client-only) 的全局 mixin 来实现：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Vue.mixin(&#123;</span><br><span class="line">  beforeMount () &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> &#123; asyncData &#125; = <span class="hljs-keyword">this</span>.$options</span><br><span class="line">    <span class="hljs-keyword">if</span> (asyncData) &#123;</span><br><span class="line">      <span class="hljs-comment">// 将获取数据操作分配给 promise</span></span><br><span class="line">      <span class="hljs-comment">// 以便在组件中，我们可以在数据准备就绪后</span></span><br><span class="line">      <span class="hljs-comment">// 通过运行 `this.dataPromise.then(...)` 来执行其他任务</span></span><br><span class="line">      <span class="hljs-keyword">this</span>.dataPromise = asyncData(&#123;</span><br><span class="line">        store: <span class="hljs-keyword">this</span>.$store,</span><br><span class="line">        route: <span class="hljs-keyword">this</span>.$route</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>这两种策略是根本上不同的用户体验决策，应该根据你创建的应用程序的实际使用场景进行挑选。但是无论你选择哪种策略，当路由组件重用（同一路由，但是 params 或 query 已更改，例如，从 <code>user/1</code> 到 <code>user/2</code>）时，也应该调用 <code>asyncData</code> 函数。我们也可以通过纯客户端 (client-only) 的全局 mixin 来处理这个问题：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Vue.mixin(&#123;</span><br><span class="line">  beforeRouteUpdate (to, <span class="hljs-keyword">from</span>, next) &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> &#123; asyncData &#125; = <span class="hljs-keyword">this</span>.$options</span><br><span class="line">    <span class="hljs-keyword">if</span> (asyncData) &#123;</span><br><span class="line">      asyncData(&#123;</span><br><span class="line">        store: <span class="hljs-keyword">this</span>.$store,</span><br><span class="line">        route: to</span><br><span class="line">      &#125;).then(next).catch(next)</span><br><span class="line">    &#125; <span class="hljs-keyword">else</span> &#123;</span><br><span class="line">      next()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="Store-代码拆分-Store-Code-Splitting"><a href="#Store-代码拆分-Store-Code-Splitting" class="headerlink" title="Store 代码拆分 (Store Code Splitting)"></a>Store 代码拆分 (Store Code Splitting)</h2><p>在大型应用程序中，我们的 Vuex store 可能会分为多个模块。当然，也可以将这些模块代码，分割到相应的路由组件 chunk 中。假设我们有以下 store 模块：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// store/modules/foo.js</span></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> &#123;</span><br><span class="line">  namespaced: <span class="hljs-literal">true</span>,</span><br><span class="line">  <span class="hljs-comment">// 重要信息：state 必须是一个函数，</span></span><br><span class="line">  <span class="hljs-comment">// 因此可以创建多个实例化该模块</span></span><br><span class="line">  state: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> (&#123;</span><br><span class="line">    count: <span class="hljs-number">0</span></span><br><span class="line">  &#125;),</span><br><span class="line">  actions: &#123;</span><br><span class="line">    inc: <span class="hljs-function">(<span class="hljs-params">&#123; commit &#125;</span>) =&gt;</span> commit(<span class="hljs-string">'inc'</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">  mutations: &#123;</span><br><span class="line">    inc: <span class="hljs-function"><span class="hljs-params">state</span> =&gt;</span> state.count++</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以在路由组件的 <code>asyncData</code> 钩子函数中，使用 <code>store.registerModule</code> 惰性注册(lazy-register)这个模块：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">// 在路由组件内</span><br><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>&#123;&#123; fooCount &#125;&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span></span><br><span class="line"><span class="hljs-actionscript"><span class="hljs-comment">// 在这里导入模块，而不是在 `store/index.js` 中</span></span></span><br><span class="line"><span class="hljs-javascript"><span class="hljs-keyword">import</span> fooStoreModule <span class="hljs-keyword">from</span> <span class="hljs-string">'../store/modules/foo'</span></span></span><br><span class="line"></span><br><span class="line"><span class="hljs-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> &#123;</span></span><br><span class="line">  asyncData (&#123; store &#125;) &#123;</span><br><span class="line"><span class="hljs-actionscript">    store.registerModule(<span class="hljs-string">'foo'</span>, fooStoreModule)</span></span><br><span class="line"><span class="hljs-actionscript">    <span class="hljs-keyword">return</span> store.dispatch(<span class="hljs-string">'foo/inc'</span>)</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line"><span class="hljs-actionscript">  <span class="hljs-comment">// 重要信息：当多次访问路由时，</span></span></span><br><span class="line"><span class="hljs-actionscript">  <span class="hljs-comment">// 避免在客户端重复注册模块。</span></span></span><br><span class="line">  destroyed () &#123;</span><br><span class="line"><span class="hljs-actionscript">    <span class="hljs-keyword">this</span>.$store.unregisterModule(<span class="hljs-string">'foo'</span>)</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  computed: &#123;</span><br><span class="line">    fooCount () &#123;</span><br><span class="line"><span class="hljs-actionscript">      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.$store.state.foo.count</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>由于模块现在是路由组件的依赖，所以它将被 webpack 移动到路由组件的异步 chunk 中。</p><h1 id="客户端激活-client-side-hydration"><a href="#客户端激活-client-side-hydration" class="headerlink" title="客户端激活 (client-side hydration)"></a>客户端激活 (client-side hydration)</h1><p>所谓客户端激活，指的是 Vue 在浏览器端接管由服务端发送的静态 HTML，使其变为由 Vue 管理的动态 DOM 的过程。</p><p>在 <code>entry-client.js</code> 中，我们用下面这行挂载(mount)应用程序：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// 这里假定 App.vue template 根元素的 `id="app"`</span></span><br><span class="line">app.$mount(<span class="hljs-string">'#app'</span>)</span><br></pre></td></tr></table></figure><p>由于服务器已经渲染好了 HTML，我们显然无需将其丢弃再重新创建所有的 DOM 元素。相反，我们需要”激活”这些静态的 HTML，然后使他们成为动态的（能够响应后续的数据变化）。</p><p>如果你检查服务器渲染的输出结果，你会注意到应用程序的根元素上添加了一个特殊的属性：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span> <span class="hljs-attr">data-server-rendered</span>=<span class="hljs-string">"true"</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>data-server-rendered</code> 特殊属性，让客户端 Vue 知道这部分 HTML 是由 Vue 在服务端渲染的，并且应该以激活模式进行挂载。注意，这里并没有添加 <code>id=&quot;app&quot;</code>，而是添加 <code>data-server-rendered</code> 属性：你需要自行添加 ID 或其他能够选取到应用程序根元素的选择器，否则应用程序将无法正常激活。</p><p>注意，在没有 <code>data-server-rendered</code> 属性的元素上，还可以向 <code>$mount</code> 函数的 <code>hydrating</code> 参数位置传入 <code>true</code>，来强制使用激活模式(hydration)：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// 强制使用应用程序的激活模式</span></span><br><span class="line">app.$mount(<span class="hljs-string">'#app'</span>, <span class="hljs-literal">true</span>)</span><br></pre></td></tr></table></figure><p>在开发模式下，Vue 将推断客户端生成的虚拟 DOM 树 (virtual DOM tree)，是否与从服务器渲染的 DOM 结构 (DOM structure) 匹配。如果无法匹配，它将退出混合模式，丢弃现有的 DOM 并从头开始渲染。<strong>在生产模式下，此检测会被跳过，以避免性能损耗。</strong></p><h3 id="一些需要注意的坑"><a href="#一些需要注意的坑" class="headerlink" title="一些需要注意的坑"></a>一些需要注意的坑</h3><p>使用「SSR + 客户端混合」时，需要了解的一件事是，浏览器可能会更改的一些特殊的 HTML 结构。例如，当你在 Vue 模板中写入：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">table</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>hi<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span></span><br></pre></td></tr></table></figure><p>浏览器会在 <code>&lt;table&gt;</code> 内部自动注入 <code>&lt;tbody&gt;</code>，然而，由于 Vue 生成的虚拟 DOM (virtual DOM) 不包含 <code>&lt;tbody&gt;</code>，所以会导致无法匹配。为能够正确匹配，请确保在模板中写入有效的 HTML。</p><h1 id="Bundle-Renderer-指引"><a href="#Bundle-Renderer-指引" class="headerlink" title="Bundle Renderer 指引"></a>Bundle Renderer 指引</h1><h2 id="使用基本-SSR-的问题"><a href="#使用基本-SSR-的问题" class="headerlink" title="使用基本 SSR 的问题"></a>使用基本 SSR 的问题</h2><p>到目前为止，我们假设打包的服务器端代码，将由服务器通过 <code>require</code> 直接使用：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> createApp = <span class="hljs-built_in">require</span>(<span class="hljs-string">'/path/to/built-server-bundle.js'</span>)</span><br></pre></td></tr></table></figure><p>这是理所应当的，然而在每次编辑过应用程序源代码之后，都必须停止并重启服务。这在开发过程中会影响开发效率。此外，Node.js 本身不支持 source map。</p><h2 id="传入-BundleRenderer"><a href="#传入-BundleRenderer" class="headerlink" title="传入 BundleRenderer"></a>传入 BundleRenderer</h2><p><code>vue-server-renderer</code> 提供一个名为 <code>createBundleRenderer</code> 的 API，用于处理此问题，通过使用 webpack 的自定义插件，server bundle 将生成为可传递到 bundle renderer 的特殊 JSON 文件。所创建的 bundle renderer，用法和普通 renderer 相同，但是 bundle renderer 提供以下优点：</p><ul><li>内置的 source map 支持（在 webpack 配置中使用 <code>devtool: &#39;source-map&#39;</code>）</li><li>在开发环境甚至部署过程中热重载（通过读取更新后的 bundle，然后重新创建 renderer 实例）</li><li>关键 CSS(critical CSS) 注入（在使用 <code>*.vue</code> 文件时）：自动内联在渲染过程中用到的组件所需的CSS。更多细节请查看 <a href="https://ssr.vuejs.org/zh/guide/css.html" target="_blank" rel="noopener">CSS</a> 章节。</li><li>使用 <a href="https://ssr.vuejs.org/zh/api/#clientmanifest" target="_blank" rel="noopener">clientManifest</a> 进行资源注入：自动推断出最佳的预加载(preload)和预取(prefetch)指令，以及初始渲染所需的代码分割 chunk。</li></ul><hr><p>在下一章节中，我们将讨论如何配置 webpack，以生成 bundle renderer 所需的构建工件 (build artifact)，但现在假设我们已经有了这些需要的构建工件，以下就是创建和使用 bundle renderer 的方法：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> &#123; createBundleRenderer &#125; = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> renderer = createBundleRenderer(serverBundle, &#123;</span><br><span class="line">  runInNewContext: <span class="hljs-literal">false</span>, <span class="hljs-comment">// 推荐</span></span><br><span class="line">  template, <span class="hljs-comment">// （可选）页面模板</span></span><br><span class="line">  clientManifest <span class="hljs-comment">// （可选）客户端构建 manifest</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 在服务器处理函数中……</span></span><br><span class="line">server.get(<span class="hljs-string">'*'</span>, (req, res) =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">const</span> context = &#123; <span class="hljs-attr">url</span>: req.url &#125;</span><br><span class="line">  <span class="hljs-comment">// 这里无需传入一个应用程序，因为在执行 bundle 时已经自动创建过。</span></span><br><span class="line">  <span class="hljs-comment">// 现在我们的服务器与应用程序已经解耦！</span></span><br><span class="line">  renderer.renderToString(context, (err, html) =&gt; &#123;</span><br><span class="line">    <span class="hljs-comment">// 处理异常……</span></span><br><span class="line">    res.end(html)</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>bundle renderer 在调用 <code>renderToString</code> 时，它将自动执行「由 bundle 创建的应用程序实例」所导出的函数（传入<code>上下文</code>作为参数），然后渲染它。</p><p>注意，推荐将 <code>runInNewContext</code> 选项设置为 <code>false</code> 或 <code>&#39;once&#39;</code>。</p><h1 id="构建配置"><a href="#构建配置" class="headerlink" title="构建配置"></a>构建配置</h1><p>我们假设你已经知道，如何为纯客户端 (client-only) 项目配置 webpack。服务器端渲染 (SSR) 项目的配置大体上与纯客户端项目类似，但是我们建议将配置分为三个文件：<em>base</em>, <em>client</em> 和 <em>server</em>。基本配置 (base config) 包含在两个环境共享的配置，例如，输出路径 (output path)，别名 (alias) 和 loader。服务器配置 (server config) 和客户端配置 (client config)，可以通过使用 <a href="https://github.com/survivejs/webpack-merge" target="_blank" rel="noopener">webpack-merge</a> 来简单地扩展基本配置。</p><h2 id="服务器配置-Server-Config"><a href="#服务器配置-Server-Config" class="headerlink" title="服务器配置 (Server Config)"></a>服务器配置 (Server Config)</h2><p>服务器配置，是用于生成传递给 <code>createBundleRenderer</code> 的 server bundle。它应该是这样的：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> merge = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack-merge'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> nodeExternals = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack-node-externals'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> baseConfig = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./webpack.base.config.js'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> VueSSRServerPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer/server-plugin'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-built_in">module</span>.exports = merge(baseConfig, &#123;</span><br><span class="line">  <span class="hljs-comment">// 将 entry 指向应用程序的 server entry 文件</span></span><br><span class="line">  entry: <span class="hljs-string">'/path/to/entry-server.js'</span>,</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import)，</span></span><br><span class="line">  <span class="hljs-comment">// 并且还会在编译 Vue 组件时，</span></span><br><span class="line">  <span class="hljs-comment">// 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。</span></span><br><span class="line">  target: <span class="hljs-string">'node'</span>,</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 对 bundle renderer 提供 source map 支持</span></span><br><span class="line">  devtool: <span class="hljs-string">'source-map'</span>,</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)</span></span><br><span class="line">  output: &#123;</span><br><span class="line">    libraryTarget: <span class="hljs-string">'commonjs2'</span></span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// https://webpack.js.org/configuration/externals/#function</span></span><br><span class="line">  <span class="hljs-comment">// https://github.com/liady/webpack-node-externals</span></span><br><span class="line">  <span class="hljs-comment">// 外置化应用程序依赖模块。可以使服务器构建速度更快，</span></span><br><span class="line">  <span class="hljs-comment">// 并生成较小的 bundle 文件。</span></span><br><span class="line">  externals: nodeExternals(&#123;</span><br><span class="line">    <span class="hljs-comment">// 不要外置化 webpack 需要处理的依赖模块。</span></span><br><span class="line">    <span class="hljs-comment">// 你可以在这里添加更多的文件类型。例如，未处理 *.vue 原始文件，</span></span><br><span class="line">    <span class="hljs-comment">// 你还应该将修改 `global`（例如 polyfill）的依赖模块列入白名单</span></span><br><span class="line">    whitelist: <span class="hljs-regexp">/\.css$/</span></span><br><span class="line">  &#125;),</span><br><span class="line"></span><br><span class="line">  <span class="hljs-comment">// 这是将服务器的整个输出</span></span><br><span class="line">  <span class="hljs-comment">// 构建为单个 JSON 文件的插件。</span></span><br><span class="line">  <span class="hljs-comment">// 默认文件名为 `vue-ssr-server-bundle.json`</span></span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="hljs-keyword">new</span> VueSSRServerPlugin()</span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>在生成 <code>vue-ssr-server-bundle.json</code> 之后，只需将文件路径传递给 <code>createBundleRenderer</code>：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> &#123; createBundleRenderer &#125; = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> renderer = createBundleRenderer(<span class="hljs-string">'/path/to/vue-ssr-server-bundle.json'</span>, &#123;</span><br><span class="line">  <span class="hljs-comment">// ……renderer 的其他选项</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>又或者，你还可以将 bundle 作为对象传递给 <code>createBundleRenderer</code>。这对开发过程中的热重载是很有用的 - 具体请查看 HackerNews demo 的<a href="https://github.com/vuejs/vue-hackernews-2.0/blob/master/build/setup-dev-server.js" target="_blank" rel="noopener">参考设置</a>。</p><h3 id="扩展说明-Externals-Caveats"><a href="#扩展说明-Externals-Caveats" class="headerlink" title="扩展说明 (Externals Caveats)"></a>扩展说明 (Externals Caveats)</h3><p>请注意，在 <code>externals</code> 选项中，我们将 CSS 文件列入白名单。这是因为从依赖模块导入的 CSS 还应该由 webpack 处理。如果你导入依赖于 webpack 的任何其他类型的文件（例如 <code>*.vue</code>, <code>*.sass</code>），那么你也应该将它们添加到白名单中。</p><p>如果你使用 <code>runInNewContext: &#39;once&#39;</code> 或 <code>runInNewContext: true</code>，那么你还应该将修改 <code>global</code>的 polyfill 列入白名单，例如 <code>babel-polyfill</code>。这是因为当使用新的上下文模式时，<strong>server bundle 中的代码具有自己的 <code>global</code> 对象。</strong>由于在使用 Node 7.6+ 时，在服务器并不真正需要它，所以实际上只需在客户端 entry 导入它。</p><h2 id="客户端配置-Client-Config"><a href="#客户端配置-Client-Config" class="headerlink" title="客户端配置 (Client Config)"></a>客户端配置 (Client Config)</h2><p>客户端配置 (client config) 和基本配置 (base config) 大体上相同。显然你需要把 <code>entry</code> 指向你的客户端入口文件。除此之外，如果你使用 <code>CommonsChunkPlugin</code>，请确保仅在客户端配置 (client config) 中使用，因为服务器包需要单独的入口 chunk。</p><h3 id="生成-clientManifest"><a href="#生成-clientManifest" class="headerlink" title="生成 clientManifest"></a>生成 <code>clientManifest</code></h3><blockquote><p>需要版本 2.3.0+</p></blockquote><p>除了 server bundle 之外，我们还可以生成客户端构建清单 (client build manifest)。使用客户端清单 (client manifest) 和服务器 bundle(server bundle)，renderer 现在具有了<em>服务器和客户端</em>的构建信息，因此它可以自动推断和注入<a href="https://css-tricks.com/prefetching-preloading-prebrowsing/" target="_blank" rel="noopener">资源预加载 / 数据预取指令(preload / prefetch directive)</a>，以及 css 链接 / script 标签到所渲染的 HTML。</p><p>好处是双重的：</p><ol><li>在生成的文件名中有哈希时，可以取代 <code>html-webpack-plugin</code> 来注入正确的资源 URL。</li><li>在通过 webpack 的按需代码分割特性渲染 bundle 时，我们可以确保对 chunk 进行最优化的资源预加载/数据预取，并且还可以将所需的异步 chunk 智能地注入为 <code>&lt;script&gt;</code> 标签，以避免客户端的瀑布式请求 (waterfall request)，以及改善可交互时间 (TTI - time-to-interactive)。</li></ol><p>要使用客户端清单 (client manifest)，客户端配置 (client config) 将如下所示：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> webpack = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> merge = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack-merge'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> baseConfig = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./webpack.base.config.js'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> VueSSRClientPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer/client-plugin'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-built_in">module</span>.exports = merge(baseConfig, &#123;</span><br><span class="line">  entry: <span class="hljs-string">'/path/to/entry-client.js'</span>,</span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="hljs-comment">// 重要信息：这将 webpack 运行时分离到一个引导 chunk 中，</span></span><br><span class="line">    <span class="hljs-comment">// 以便可以在之后正确注入异步 chunk。</span></span><br><span class="line">    <span class="hljs-comment">// 这也为你的 应用程序/vendor 代码提供了更好的缓存。</span></span><br><span class="line">    <span class="hljs-keyword">new</span> webpack.optimize.CommonsChunkPlugin(&#123;</span><br><span class="line">      name: <span class="hljs-string">"manifest"</span>,</span><br><span class="line">      minChunks: <span class="hljs-literal">Infinity</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="hljs-comment">// 此插件在输出目录中</span></span><br><span class="line">    <span class="hljs-comment">// 生成 `vue-ssr-client-manifest.json`。</span></span><br><span class="line">    <span class="hljs-keyword">new</span> VueSSRClientPlugin()</span><br><span class="line">  ]</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>然后，你就可以使用生成的客户端清单 (client manifest) 以及页面模板：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> &#123; createBundleRenderer &#125; = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> template = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>).readFileSync(<span class="hljs-string">'/path/to/template.html'</span>, <span class="hljs-string">'utf-8'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> serverBundle = <span class="hljs-built_in">require</span>(<span class="hljs-string">'/path/to/vue-ssr-server-bundle.json'</span>)</span><br><span class="line"><span class="hljs-keyword">const</span> clientManifest = <span class="hljs-built_in">require</span>(<span class="hljs-string">'/path/to/vue-ssr-client-manifest.json'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> renderer = createBundleRenderer(serverBundle, &#123;</span><br><span class="line">  template,</span><br><span class="line">  clientManifest</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>通过以上设置，使用代码分割特性构建后的服务器渲染的 HTML 代码，将看起来如下（所有都是自动注入）：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!-- 用于当前渲染的 chunk 会被资源预加载(preload) --&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/manifest.js"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"script"</span>&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/main.js"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"script"</span>&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"preload"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/0.js"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"script"</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!-- 未用到的异步 chunk 会被数据预取(prefetch)（次要优先级） --&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"prefetch"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/1.js"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"script"</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!-- 应用程序内容 --&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">data-server-rendered</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>async<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!-- manifest chunk 优先 --&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/manifest.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!-- 在主 chunk 之前注入异步 chunk --&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/0.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/main.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="手动资源注入-Manual-Asset-Injection"><a href="#手动资源注入-Manual-Asset-Injection" class="headerlink" title="手动资源注入(Manual Asset Injection)"></a>手动资源注入(Manual Asset Injection)</h3><p>默认情况下，当提供 <code>template</code> 渲染选项时，资源注入是自动执行的。但是有时候，你可能需要对资源注入的模板进行更细粒度 (finer-grained) 的控制，或者你根本不使用模板。在这种情况下，你可以在创建 renderer 并手动执行资源注入时，传入 <code>inject: false</code>。</p><p>在 <code>renderToString</code> 回调函数中，你传入的 <code>context</code> 对象会暴露以下方法：</p><ul><li><p><code>context.renderStyles()</code></p><p>这将返回内联 <code>&lt;style&gt;</code> 标签包含所有关键 CSS(critical CSS) ，其中关键 CSS 是在要用到的 <code>*.vue</code>组件的渲染过程中收集的。有关更多详细信息，请查看 <a href="https://ssr.vuejs.org/zh/guide/css.html" target="_blank" rel="noopener">CSS 管理</a>。</p><p>如果提供了 <code>clientManifest</code>，返回的字符串中，也将包含着 <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code> 标签内由 webpack 输出(webpack-emitted)的 CSS 文件（例如，使用 <code>extract-text-webpack-plugin</code> 提取的 CSS，或使用 <code>file-loader</code> 导入的 CSS）</p></li><li><p><code>context.renderState(options?: Object)</code></p><p>此方法序列化 <code>context.state</code> 并返回一个内联的 script，其中状态被嵌入在 <code>window.__INITIAL_STATE__</code> 中。</p><p>上下文状态键 (context state key) 和 window 状态键 (window state key)，都可以通过传递选项对象进行自定义：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">context.renderState(&#123;</span><br><span class="line">  contextKey: <span class="hljs-string">'myCustomState'</span>,</span><br><span class="line">  windowKey: <span class="hljs-string">'__MY_STATE__'</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// -&gt; &lt;script&gt;window.__MY_STATE__=&#123;...&#125;&lt;/script&gt;</span></span><br></pre></td></tr></table></figure></li><li><p><code>context.renderScripts()</code></p><ul><li>需要 <code>clientManifest</code></li></ul><p>此方法返回引导客户端应用程序所需的 <code>&lt;script&gt;</code> 标签。当在应用程序代码中使用异步代码分割 (async code-splitting) 时，此方法将智能地正确的推断需要引入的那些异步 chunk。</p></li><li><p><code>context.renderResourceHints()</code></p><ul><li>需要 <code>clientManifest</code></li></ul><p>此方法返回当前要渲染的页面，所需的 <code>&lt;link rel=&quot;preload/prefetch&quot;&gt;</code> 资源提示 (resource hint)。默认情况下会：</p><ul><li>预加载页面所需的 JavaScript 和 CSS 文件</li><li>预取异步 JavaScript chunk，之后可能会用于渲染</li></ul><p>使用 <a href="https://ssr.vuejs.org/zh/api/#shouldpreload" target="_blank" rel="noopener"><code>shouldPreload</code></a> 选项可以进一步自定义要预加载的文件。</p></li><li><p><code>context.getPreloadFiles()</code></p><ul><li>需要 <code>clientManifest</code></li></ul><p>此方法不返回字符串 - 相反，它返回一个数组，此数组是由要预加载的资源文件对象所组成。这可以用在以编程方式 (programmatically) 执行 HTTP/2 服务器推送 (HTTP/2 server push)。</p></li></ul><p>由于传递给 <code>createBundleRenderer</code> 的 <code>template</code> 将会使用 <code>context</code> 对象进行插值，你可以（通过传入 <code>inject: false</code>）在模板中使用这些方法：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) --&gt;</span></span><br><span class="line">    &#123;&#123;&#123; renderResourceHints() &#125;&#125;&#125;</span><br><span class="line">    &#123;&#123;&#123; renderStyles() &#125;&#125;&#125;</span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span></span><br><span class="line">    <span class="hljs-comment">&lt;!--vue-ssr-outlet--&gt;</span></span><br><span class="line">    &#123;&#123;&#123; renderState() &#125;&#125;&#125;</span><br><span class="line">    &#123;&#123;&#123; renderScripts() &#125;&#125;&#125;</span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如果你根本没有使用 <code>template</code>，你可以自己拼接字符串。</p><h1 id="CSS-管理"><a href="#CSS-管理" class="headerlink" title="CSS 管理"></a>CSS 管理</h1><p>管理 CSS 的推荐方法是简单地使用 <code>*.vue</code> 单个文件组件内的 <code>&lt;style&gt;</code>，它提供：</p><ul><li>与 HTML 并列同级，组件作用域 CSS</li><li>能够使用预处理器(pre-processor)或 PostCSS</li><li>开发过程中热重载(hot-reload)</li></ul><p>更重要的是，<code>vue-style-loader</code>（<code>vue-loader</code> 内部使用的 loader），具备一些服务器端渲染的特殊功能：</p><ul><li><p>客户端和服务器端的通用编程体验。</p></li><li><p>在使用 <code>bundleRenderer</code> 时，自动注入关键 CSS(critical CSS)。</p><p>如果在服务器端渲染期间使用，可以在 HTML 中收集和内联（使用 <code>template</code> 选项时自动处理）组件的 CSS。在客户端，当第一次使用该组件时，<code>vue-style-loader</code> 会检查这个组件是否已经具有服务器内联(server-inlined)的 CSS - 如果没有，CSS 将通过 <code>&lt;style&gt;</code> 标签动态注入。</p></li><li><p>通用 CSS 提取。</p><p>此设置支持使用 <a href="https://github.com/webpack-contrib/extract-text-webpack-plugin" target="_blank" rel="noopener"><code>extract-text-webpack-plugin</code></a> 将主 chunk(main chunk) 中的 CSS 提取到单独的 CSS 文件中（使用 <code>template</code> 自动注入），这样可以将文件分开缓存。建议用于存在很多公用 CSS 时。</p><p>内部异步组件中的 CSS 将内联为 JavaScript 字符串，并由 <code>vue-style-loader</code> 处理。</p></li></ul><h2 id="启用-CSS-提取"><a href="#启用-CSS-提取" class="headerlink" title="启用 CSS 提取"></a>启用 CSS 提取</h2><p>要从 <code>*.vue</code> 文件中提取 CSS，可以使用 <code>vue-loader</code> 的 <code>extractCSS</code> 选项（需要 <code>vue-loader</code>12.0.0+）</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// webpack.config.js</span></span><br><span class="line"><span class="hljs-keyword">const</span> ExtractTextPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'extract-text-webpack-plugin'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// CSS 提取应该只用于生产环境</span></span><br><span class="line"><span class="hljs-comment">// 这样我们在开发过程中仍然可以热重载</span></span><br><span class="line"><span class="hljs-keyword">const</span> isProduction = process.env.NODE_ENV === <span class="hljs-string">'production'</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="hljs-comment">// ...</span></span><br><span class="line">  <span class="hljs-built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="hljs-regexp">/\.vue$/</span>,</span><br><span class="line">        loader: <span class="hljs-string">'vue-loader'</span>,</span><br><span class="line">        options: &#123;</span><br><span class="line">          <span class="hljs-comment">// enable CSS extraction</span></span><br><span class="line">          extractCSS: isProduction</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="hljs-comment">// ...</span></span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  plugins: isProduction</span><br><span class="line">    <span class="hljs-comment">// 确保添加了此插件！</span></span><br><span class="line">    ? [<span class="hljs-keyword">new</span> ExtractTextPlugin(&#123; <span class="hljs-attr">filename</span>: <span class="hljs-string">'common.[chunkhash].css'</span> &#125;)]</span><br><span class="line">    : []</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>请注意，上述配置仅适用于 <code>*.vue</code> 文件中的样式，然而你也可以使用 <code>&lt;style src=&quot;./foo.css&quot;&gt;</code> 将外部 CSS 导入 Vue 组件。</p><p>如果你想从 JavaScript 中导入 CSS，例如，<code>import &#39;foo.css&#39;</code>，你需要配置合适的 loader：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="hljs-comment">// ...</span></span><br><span class="line">  <span class="hljs-built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="hljs-regexp">/\.css$/</span>,</span><br><span class="line">        <span class="hljs-comment">// 重要：使用 vue-style-loader 替代 style-loader</span></span><br><span class="line">        use: isProduction</span><br><span class="line">          ? ExtractTextPlugin.extract(&#123;</span><br><span class="line">              use: <span class="hljs-string">'css-loader'</span>,</span><br><span class="line">              fallback: <span class="hljs-string">'vue-style-loader'</span></span><br><span class="line">            &#125;)</span><br><span class="line">          : [<span class="hljs-string">'vue-style-loader'</span>, <span class="hljs-string">'css-loader'</span>]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="hljs-comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="从依赖模块导入样式"><a href="#从依赖模块导入样式" class="headerlink" title="从依赖模块导入样式"></a>从依赖模块导入样式</h2><p>从 NPM 依赖模块导入 CSS 时需要注意的几点：</p><ol><li>在服务器端构建过程中，不应该外置化提取。</li><li>在使用 CSS 提取 + 使用 <code>CommonsChunkPlugin</code> 插件提取 vendor 时，如果提取的 CSS 位于提取的 vendor chunk 之中，<code>extract-text-webpack-plugin</code> 会遇到问题。为了解决这个问题，请避免在 vendor chunk 中包含 CSS 文件。客户端 webpack 配置示例如下：</li></ol><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="hljs-comment">// ...</span></span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="hljs-comment">// 将依赖模块提取到 vendor chunk 以获得更好的缓存，是很常见的做法。</span></span><br><span class="line">    <span class="hljs-keyword">new</span> webpack.optimize.CommonsChunkPlugin(&#123;</span><br><span class="line">      name: <span class="hljs-string">'vendor'</span>,</span><br><span class="line">      minChunks: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">module</span>) </span>&#123;</span><br><span class="line">        <span class="hljs-comment">// 一个模块被提取到 vendor chunk 时……</span></span><br><span class="line">        <span class="hljs-keyword">return</span> (</span><br><span class="line">          <span class="hljs-comment">// 如果它在 node_modules 中</span></span><br><span class="line">          /node_modules/.test(<span class="hljs-built_in">module</span>.context) &amp;&amp;</span><br><span class="line">          <span class="hljs-comment">// 如果 request 是一个 CSS 文件，则无需外置化提取</span></span><br><span class="line">          !<span class="hljs-regexp">/\.css$/</span>.test(<span class="hljs-built_in">module</span>.request)</span><br><span class="line">        )</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="hljs-comment">// 提取 webpack 运行时和 manifest</span></span><br><span class="line">    <span class="hljs-keyword">new</span> webpack.optimize.CommonsChunkPlugin(&#123;</span><br><span class="line">      name: <span class="hljs-string">'manifest'</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="hljs-comment">// ...</span></span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="Head-管理"><a href="#Head-管理" class="headerlink" title="Head 管理"></a>Head 管理</h1><p>类似于资源注入，Head 管理遵循相同的理念：我们可以在组件的生命周期中，将数据动态地追加到渲染<code>上下文</code> (render <code>context</code>)，然后在<code>模板</code>中的占位符替换为这些数据。</p><blockquote><p>在 2.3.2+ 的版本，你可以通过 <code>this.$ssrContext</code> 来直接访问组件中的服务器端渲染上下文(SSR context)。在旧版本中，你必须通过将其传递给 <code>createApp()</code> 并将其暴露于根实例的 <code>$options</code> 上，才能手动注入服务器端渲染上下文(SSR context) - 然后子组件可以通过 <code>this.$root.$options.ssrContext</code> 来访问它。</p></blockquote><p>我们可以编写一个简单的 mixin 来完成标题管理：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// title-mixin.js</span></span><br><span class="line"></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTitle</span> (<span class="hljs-params">vm</span>) </span>&#123;</span><br><span class="line">  <span class="hljs-comment">// 组件可以提供一个 `title` 选项</span></span><br><span class="line">  <span class="hljs-comment">// 此选项可以是一个字符串或函数</span></span><br><span class="line">  <span class="hljs-keyword">const</span> &#123; title &#125; = vm.$options</span><br><span class="line">  <span class="hljs-keyword">if</span> (title) &#123;</span><br><span class="line">    <span class="hljs-keyword">return</span> <span class="hljs-keyword">typeof</span> title === <span class="hljs-string">'function'</span></span><br><span class="line">      ? title.call(vm)</span><br><span class="line">      : title</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> serverTitleMixin = &#123;</span><br><span class="line">  created () &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> title = getTitle(<span class="hljs-keyword">this</span>)</span><br><span class="line">    <span class="hljs-keyword">if</span> (title) &#123;</span><br><span class="line">      <span class="hljs-keyword">this</span>.$ssrContext.title = title</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> clientTitleMixin = &#123;</span><br><span class="line">  mounted () &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> title = getTitle(<span class="hljs-keyword">this</span>)</span><br><span class="line">    <span class="hljs-keyword">if</span> (title) &#123;</span><br><span class="line">      <span class="hljs-built_in">document</span>.title = title</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 可以通过 `webpack.DefinePlugin` 注入 `VUE_ENV`</span></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> process.env.VUE_ENV === <span class="hljs-string">'server'</span></span><br><span class="line">  ? serverTitleMixin</span><br><span class="line">  : clientTitleMixin</span><br></pre></td></tr></table></figure><p>现在，路由组件可以利用以上 mixin，来控制文档标题 (document title)：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// Item.vue</span></span><br><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> &#123;</span><br><span class="line">  mixins: [titleMixin],</span><br><span class="line">  title () &#123;</span><br><span class="line">    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.item.title</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  asyncData (&#123; store, route &#125;) &#123;</span><br><span class="line">    <span class="hljs-keyword">return</span> store.dispatch(<span class="hljs-string">'fetchItem'</span>, route.params.id)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  computed: &#123;</span><br><span class="line">    item () &#123;</span><br><span class="line">      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.$store.state.items[<span class="hljs-keyword">this</span>.$route.params.id]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后模板中的内容将会传递给 bundle renderer：</p><figure class="highlight html hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>&#123;&#123; title &#125;&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span><br><span class="line">  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span></span><br><span class="line">    ...</span><br><span class="line">  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span><br><span class="line"><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><ul><li>使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation)，以避免 XSS 攻击。</li><li>你应该在创建 <code>context</code> 对象时提供一个默认标题，以防在渲染过程中组件没有设置标题。</li></ul><hr><p>使用相同的策略，你可以轻松地将此 mixin 扩展为通用的头部管理工具 (generic head management utility)。</p><h1 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h1><p>虽然 Vue 的服务器端渲染 (SSR) 相当快速，但是由于创建组件实例和虚拟 DOM 节点的开销，无法与纯基于字符串拼接 (pure string-based) 的模板的性能相当。在 SSR 性能至关重要的情况下，明智地利用缓存策略，可以极大改善响应时间并减少服务器负载。</p><h2 id="页面级别缓存-Page-level-Caching"><a href="#页面级别缓存-Page-level-Caching" class="headerlink" title="页面级别缓存 (Page-level Caching)"></a>页面级别缓存 (Page-level Caching)</h2><p>在大多数情况下，服务器渲染的应用程序依赖于外部数据，因此本质上页面内容是动态的，不能持续长时间缓存。然而，如果内容不是用户特定 (user-specific)（即对于相同的 URL，总是为所有用户渲染相同的内容），我们可以利用名为 <a href="https://www.nginx.com/blog/benefits-of-microcaching-nginx/" target="_blank" rel="noopener">micro-caching</a> 的缓存策略，来大幅度提高应用程序处理高流量的能力。</p><p>这通常在 Nginx 层完成，但是我们也可以在 Node.js 中实现它：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> microCache = LRU(&#123;</span><br><span class="line">  max: <span class="hljs-number">100</span>,</span><br><span class="line">  maxAge: <span class="hljs-number">1000</span> <span class="hljs-comment">// 重要提示：条目在 1 秒后过期。</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> isCacheable = <span class="hljs-function"><span class="hljs-params">req</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="hljs-comment">// 实现逻辑为，检查请求是否是用户特定(user-specific)。</span></span><br><span class="line">  <span class="hljs-comment">// 只有非用户特定 (non-user-specific) 页面才会缓存</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">server.get(<span class="hljs-string">'*'</span>, (req, res) =&gt; &#123;</span><br><span class="line">  <span class="hljs-keyword">const</span> cacheable = isCacheable(req)</span><br><span class="line">  <span class="hljs-keyword">if</span> (cacheable) &#123;</span><br><span class="line">    <span class="hljs-keyword">const</span> hit = microCache.get(req.url)</span><br><span class="line">    <span class="hljs-keyword">if</span> (hit) &#123;</span><br><span class="line">      <span class="hljs-keyword">return</span> res.end(hit)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  renderer.renderToString(<span class="hljs-function">(<span class="hljs-params">err, html</span>) =&gt;</span> &#123;</span><br><span class="line">    res.end(html)</span><br><span class="line">    <span class="hljs-keyword">if</span> (cacheable) &#123;</span><br><span class="line">      microCache.set(req.url, html)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>由于内容缓存只有一秒钟，用户将无法查看过期的内容。然而，这意味着，对于每个要缓存的页面，服务器最多只能每秒执行一次完整渲染。</p><h2 id="组件级别缓存-Component-level-Caching"><a href="#组件级别缓存-Component-level-Caching" class="headerlink" title="组件级别缓存 (Component-level Caching)"></a>组件级别缓存 (Component-level Caching)</h2><p><code>vue-server-renderer</code> 内置支持组件级别缓存 (component-level caching)。要启用组件级别缓存，你需要在创建 renderer 时提供<a href="https://ssr.vuejs.org/zh/api/#cache" target="_blank" rel="noopener">具体缓存实现方式(cache implementation)</a>。典型做法是传入 <a href="https://github.com/isaacs/node-lru-cache" target="_blank" rel="noopener">lru-cache</a>：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> LRU = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lru-cache'</span>)</span><br><span class="line"></span><br><span class="line"><span class="hljs-keyword">const</span> renderer = createRenderer(&#123;</span><br><span class="line">  cache: LRU(&#123;</span><br><span class="line">    max: <span class="hljs-number">10000</span>,</span><br><span class="line">    maxAge: ...</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>然后，你可以通过实现 <code>serverCacheKey</code> 函数来缓存组件。</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> &#123;</span><br><span class="line">  name: <span class="hljs-string">'item'</span>, <span class="hljs-comment">// 必填选项</span></span><br><span class="line">  props: [<span class="hljs-string">'item'</span>],</span><br><span class="line">  serverCacheKey: <span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> props.item.id,</span><br><span class="line">  render (h) &#123;</span><br><span class="line">    <span class="hljs-keyword">return</span> h(<span class="hljs-string">'div'</span>, <span class="hljs-keyword">this</span>.item.id)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>请注意，可缓存组件<strong>还必须定义一个唯一的 name 选项</strong>。通过使用唯一的名称，每个缓存键 (cache key) 对应一个组件：你无需担心两个组件返回同一个 key。</p><p><code>serverCacheKey</code> 返回的 key 应该包含足够的信息，来表示渲染结果的具体情况。如果渲染结果仅由 <code>props.item.id</code> 决定，则上述是一个很好的实现。但是，如果具有相同 id 的 item 可能会随时间而变化，或者如果渲染结果依赖于其他 prop，则需要修改 <code>serverCacheKey</code> 的实现，以考虑其他变量。</p><p>返回常量将导致组件始终被缓存，这对纯静态组件是有好处的。</p><h3 id="何时使用组件缓存"><a href="#何时使用组件缓存" class="headerlink" title="何时使用组件缓存"></a>何时使用组件缓存</h3><p>如果 renderer 在组件渲染过程中进行缓存命中，那么它将直接重新使用整个子树的缓存结果。这意味着在以下情况，你<strong>不</strong>应该缓存组件：</p><ul><li>它具有可能依赖于全局状态的子组件。</li><li>它具有对渲染<code>上下文</code>产生副作用(side effect)的子组件。</li></ul><p>因此，应该小心使用组件缓存来解决性能瓶颈。在大多数情况下，你不应该也不需要缓存单一实例组件。适用于缓存的最常见类型的组件，是在大的 <code>v-for</code> 列表中重复出现的组件。由于这些组件通常由数据库集合(database collection)中的对象驱动，它们可以使用简单的缓存策略：使用其唯一 id，再加上最后更新的时间戳，来生成其缓存键(cache key)：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">serverCacheKey: <span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> props.item.id + <span class="hljs-string">'::'</span> + props.item.last_updated</span><br></pre></td></tr></table></figure><h1 id="流式渲染-Streaming"><a href="#流式渲染-Streaming" class="headerlink" title="流式渲染 (Streaming)"></a>流式渲染 (Streaming)</h1><p>对于 <code>vue-server-renderer</code> 的基本 renderer 和 bundle renderer 都提供开箱即用的流式渲染功能。所有你需要做的就是，用 <code>renderToStream</code> 替代 <code>renderToString</code>：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">const</span> stream = renderer.renderToStream(context)</span><br></pre></td></tr></table></figure><p>返回的值是 <a href="https://nodejs.org/api/stream.html" target="_blank" rel="noopener">Node.js stream</a>：</p><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">let</span> html = <span class="hljs-string">''</span></span><br><span class="line"></span><br><span class="line">stream.on(<span class="hljs-string">'data'</span>, data =&gt; &#123;</span><br><span class="line">  html += data.toString()</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">stream.on(<span class="hljs-string">'end'</span>, () =&gt; &#123;</span><br><span class="line">  <span class="hljs-built_in">console</span>.log(html) <span class="hljs-comment">// 渲染完成</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">stream.on(<span class="hljs-string">'error'</span>, err =&gt; &#123;</span><br><span class="line">  <span class="hljs-comment">// handle error...</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="流式传输说明-Streaming-Caveats"><a href="#流式传输说明-Streaming-Caveats" class="headerlink" title="流式传输说明 (Streaming Caveats)"></a>流式传输说明 (Streaming Caveats)</h2><p>在流式渲染模式下，当 renderer 遍历虚拟 DOM 树 (virtual DOM tree) 时，会尽快发送数据。这意味着我们可以尽快获得”第一个 chunk”，并开始更快地将其发送给客户端。</p><p>然而，当第一个数据 chunk 被发出时，子组件甚至可能不被实例化，它们的生命周期钩子也不会被调用。这意味着，如果子组件需要在其生命周期钩子函数中，将数据附加到渲染上下文 (render context)，当流 (stream) 启动时，这些数据将不可用。这是因为，大量上下文信息 (context information)（如头信息 (head information) 或内联关键 CSS(inline critical CSS)）需要在应用程序标记 (markup) 之前出现，我们基本上必须等待流(stream)完成后，才能开始使用这些上下文数据。</p><p>因此，如果你依赖由组件生命周期钩子函数填充的上下文数据，则<strong>不建议</strong>使用流式传输模式。</p><h1 id="在非-Node-js-环境中使用"><a href="#在非-Node-js-环境中使用" class="headerlink" title="在非 Node.js 环境中使用"></a>在非 Node.js 环境中使用</h1><p><code>vue-server-renderer</code> 在默认构建时，会预先假定有一个 Node.js 环境，这使得它在其他 JavaScript 环境（如 <a href="https://github.com/phpv8/v8js" target="_blank" rel="noopener">PHP V8Js</a> 或 <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/" target="_blank" rel="noopener">Oracle Nashorn</a> 中无法使用。在 2.5+ 版本中，我们把那些基本上与环境无关的构建，编译到 <code>vue-server-renderer/basic.js</code> 中，这使得它可以在上述环境中使用。</p><p>对于所有环境，必须要预先在环境中模拟 <code>global</code> 和 <code>process</code> 对象，以及将 <code>process.env.VUE_ENV</code>设置为 <code>&quot;server&quot;</code> 和将 <code>process.env.NODE_ENV</code> 设置为 <code>&quot;development&quot;</code> 或 <code>&quot;production&quot;</code>。</p><p>在 Nashorn 环境下，可能还需要使用 Java 原生定时器，来为 <code>Promise</code> 或 <code>setTimeout</code> 提供 polyfill。</p><p>php-v8js 的示例用法：</p><figure class="highlight php hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">&lt;?php</span></span><br><span class="line">$vue_source = file_get_contents(<span class="hljs-string">'/path/to/vue.js'</span>);</span><br><span class="line">$renderer_source = file_get_contents(<span class="hljs-string">'/path/to/vue-server-renderer/basic.js'</span>);</span><br><span class="line">$app_source = file_get_contents(<span class="hljs-string">'/path/to/app.js'</span>);</span><br><span class="line"></span><br><span class="line">$v8 = <span class="hljs-keyword">new</span> V8Js();</span><br><span class="line"></span><br><span class="line">$v8-&gt;executeString(<span class="hljs-string">'var process = &#123; env: &#123; VUE_ENV: "server", NODE_ENV: "production" &#125;&#125;; this.global = &#123; process: process &#125;;'</span>);</span><br><span class="line">$v8-&gt;executeString($vue_source);</span><br><span class="line">$v8-&gt;executeString($renderer_source);</span><br><span class="line">$v8-&gt;executeString($app_source);</span><br><span class="line"><span class="hljs-meta">?&gt;</span></span><br></pre></td></tr></table></figure><hr><figure class="highlight js hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-comment">// app.js</span></span><br><span class="line"><span class="hljs-keyword">var</span> vm = <span class="hljs-keyword">new</span> Vue(&#123;</span><br><span class="line">  template: <span class="hljs-string">`&lt;div&gt;&#123;&#123; msg &#125;&#125;&lt;/div&gt;`</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    msg: <span class="hljs-string">'hello'</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">// 通过 `vue-server-renderer/basic.js` 暴露</span></span><br><span class="line">renderVueComponentToString(vm, (err, res) =&gt; &#123;</span><br><span class="line">  print(res)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><div><h1>推荐文章<span style="font-size:.45em;color:gray">（由<a href="https://github.com/huiwang/hexo-recommended-posts" target="_blank" rel="noopener">hexo文章推荐插件</a>驱动）</span></h1><ul><li><a href="http://www.borgor.cn/2019-07-25/789de642.html">在Vue-Router中使用跳转动画</a></li><li><a href="http://www.borgor.cn/2019-06-25/cbd6d728.html">使用vue+v-chart制作一个漂亮的饼状图</a></li><li><a href="http://www.borgor.cn/2019-06-14/4750a353.html">Vue-Router简明教程(进阶)</a></li></ul></div><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/kity@2.0.4/dist/kity.min.js"></script><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/kityminder-core@1.4.50/dist/kityminder.core.min.js"></script><script defer type="text/javascript" src="https://cdn.jsdelivr.net/npm/hexo-simple-mindmap@0.2.0/dist/mindmap.min.js"></script><link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/hexo-simple-mindmap@0.2.0/dist/mindmap.min.css"></div><div class="level is-size-7 is-uppercase"><div class="level-start"><div class="level-item"><span class="is-size-6 has-text-grey has-mr-7">#</span> <a class="has-link-grey -link" href="/tags/Javascript/" rel="tag">Javascript</a>, <a class="has-link-grey -link" href="/tags/Vue/" rel="tag">Vue</a>, <a class="has-link-grey -link" href="/tags/vue-ssr/" rel="tag">vue-ssr</a></div></div></div></div></div><div class="card"><div class="card-content"><h3 class="menu-label has-text-centered">喜欢这篇文章？打赏一下作者吧</h3><div class="buttons is-centered"><a class="button is-info donate"><span class="icon is-small"><i class="fab fa-alipay"></i> </span><span>支付宝</span><div class="qrcode"><img src="https://imgs.borgor.cn/imgs20190628231540.png" alt="支付宝"></div></a><a class="button is-success donate"><span class="icon is-small"><i class="fab fa-weixin"></i> </span><span>微信</span><div class="qrcode"><img src="https://imgs.borgor.cn/imgs20190628231554.png" alt="微信"></div></a></div></div></div><div class="card card-transparent"><div class="level post-navigation is-flex-wrap is-mobile"><div class="level-start"><a class="level level-item has-link-grey article-nav-prev" href="/2019-06-25/cbd6d728.html"><i class="level-item fas fa-chevron-left"></i> <span class="level-item">使用vue+v-chart制作一个漂亮的饼状图</span></a></div><div class="level-end"><a class="level level-item has-link-grey article-nav-next" href="/2019-06-14/87b1b6b1.html"><span class="level-item">Babel简介</span> <i class="level-item fas fa-chevron-right"></i></a></div></div></div><div class="card"><div class="card-content"><h3 class="title is-5 has-text-weight-normal">评论</h3><div id="valine-thread" class="content"></div><script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script><script src="//unpkg.com/valine/dist/Valine.min.js"></script><script>new Valine({el:"#valine-thread",notify:!1,verify:!1,app_id:"ybhyfHG9S4TQhIvstdj5pprw-gzGzoHsz",app_key:"ob8A13IBceQXqd8g92nUgNMx",placeholder:"你有想说的么？"})</script></div></div></div><div class="column is-4-tablet is-3-desktop is-3-widescreen has-order-1 column-left"><div class="card widget"><div class="card-content"><nav class="level"><div class="level-item has-text-centered"><div><figure class="image is-128x128 has-mb-6"><img class="is-rounded" src="https://imgs.borgor.cn/imgs20190625111723.jpg" alt="BorGor"></figure><p class="is-size-4 is-block">BorGor</p><p class="is-size-6 is-block">Note?Note!</p><p class="is-size-6 is-flex is-flex-center has-text-grey"><i class="fas fa-map-marker-alt has-mr-7"></i> <span>Lanzhou.Gansu</span></p></div></div></nav><nav class="level is-mobile"><div class="level-item has-text-centered is-marginless"><div><p class="heading">文章</p><p class="title has-text-weight-normal">86</p></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">分类</p><p class="title has-text-weight-normal">26</p></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">标签</p><p class="title has-text-weight-normal">153</p></div></div></nav><div class="level"><a class="level-item button is-link is-rounded" href="/" target="_blank">关注我</a></div><div class="level is-mobile"><a class="level-item button is-white is-marginless" target="_blank" title="Github" href="https://github.com/cyrusky"><i class="fab fa-github"></i></a></div></div></div><div class="card widget"><div class="card-content"><div class="menu"><h3 class="menu-label">分类</h3><ul class="menu-list"><li><a class="level is-marginless" href="/categories/AI/"><span class="level-start"><span class="level-item">AI</span> </span><span class="level-end"><span class="level-item tag">20</span></span></a><ul><li><a class="level is-marginless" href="/categories/AI/Machine-Learning/"><span class="level-start"><span class="level-item">Machine Learning</span> </span><span class="level-end"><span class="level-item tag">6</span></span></a></li><li><a class="level is-marginless" href="/categories/AI/Mathematics/"><span class="level-start"><span class="level-item">Mathematics</span> </span><span class="level-end"><span class="level-item tag">9</span></span></a></li><li><a class="level is-marginless" href="/categories/AI/NLP/"><span class="level-start"><span class="level-item">NLP</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-marginless" href="/categories/AI/%E5%85%AC%E5%BC%8F%E6%8E%A8%E5%AF%BC/"><span class="level-start"><span class="level-item">公式推导</span> </span><span class="level-end"><span class="level-item tag">4</span></span></a></li></ul></li><li><a class="level is-marginless" href="/categories/CheatSheets/"><span class="level-start"><span class="level-item">CheatSheets</span> </span><span class="level-end"><span class="level-item tag">4</span></span></a></li><li><a class="level is-marginless" href="/categories/DevOps/"><span class="level-start"><span class="level-item">DevOps</span> </span><span class="level-end"><span class="level-item tag">8</span></span></a><ul><li><a class="level is-marginless" href="/categories/DevOps/Anti-patterns/"><span class="level-start"><span class="level-item">Anti-patterns</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-marginless" href="/categories/DevOps/Concept/"><span class="level-start"><span class="level-item">Concept</span> </span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-marginless" href="/categories/DevOps/Continuous-Delivery/"><span class="level-start"><span class="level-item">Continuous Delivery</span> </span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-marginless" href="/categories/DevOps/TeamWorks/"><span class="level-start"><span class="level-item">TeamWorks</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-marginless" href="/categories/DevOps/Testing/"><span class="level-start"><span class="level-item">Testing</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li></ul></li><li><a class="level is-marginless" href="/categories/Development/"><span class="level-start"><span class="level-item">Development</span> </span><span class="level-end"><span class="level-item tag">33</span></span></a><ul><li><a class="level is-marginless" href="/categories/Development/Integrated/"><span class="level-start"><span class="level-item">Integrated</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-marginless" href="/categories/Development/JavaScript/"><span class="level-start"><span class="level-item">JavaScript</span> </span><span class="level-end"><span class="level-item tag">18</span></span></a></li><li><a class="level is-marginless" href="/categories/Development/Native/"><span class="level-start"><span class="level-item">Native</span> </span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-marginless" href="/categories/Development/Python/"><span class="level-start"><span class="level-item">Python</span> </span><span class="level-end"><span class="level-item tag">9</span></span></a></li><li><a class="level is-marginless" href="/categories/Development/React/"><span class="level-start"><span class="level-item">React</span> </span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-marginless" href="/categories/Development/html5/"><span class="level-start"><span class="level-item">html5</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li></ul></li><li><a class="level is-marginless" href="/categories/Operations/"><span class="level-start"><span class="level-item">Operations</span> </span><span class="level-end"><span class="level-item tag">18</span></span></a><ul><li><a class="level is-marginless" href="/categories/Operations/Nginx/"><span class="level-start"><span class="level-item">Nginx</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-marginless" href="/categories/Operations/OpenStack/"><span class="level-start"><span class="level-item">OpenStack</span> </span><span class="level-end"><span class="level-item tag">14</span></span></a></li><li><a class="level is-marginless" href="/categories/Operations/System/"><span class="level-start"><span class="level-item">System</span> </span><span class="level-end"><span class="level-item tag">3</span></span></a></li></ul></li><li><a class="level is-marginless" href="/categories/Reading-Writing/"><span class="level-start"><span class="level-item">Reading&Writing</span> </span><span class="level-end"><span class="level-item tag">3</span></span></a><ul><li><a class="level is-marginless" href="/categories/Reading-Writing/Hexo/"><span class="level-start"><span class="level-item">Hexo</span> </span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-marginless" href="/categories/Reading-Writing/LaTeX/"><span class="level-start"><span class="level-item">LaTeX</span> </span><span class="level-end"><span class="level-item tag">1</span></span></a></li></ul></li></ul></div></div></div><div class="card widget"><div class="card-content"><h3 class="menu-label">标签云</h3><a href="/tags/Agile/" style="font-size:10px">Agile</a> <a href="/tags/Angularjs/" style="font-size:11.11px">Angularjs</a> <a href="/tags/Animation/" style="font-size:10px">Animation</a> <a href="/tags/Application/" style="font-size:10px">Application</a> <a href="/tags/Artificial-Intelligence/" style="font-size:10px">Artificial Intelligence</a> <a href="/tags/BP/" style="font-size:10px">BP</a> <a href="/tags/Bokeh/" style="font-size:10px">Bokeh</a> <a href="/tags/Book/" style="font-size:10px">Book</a> <a href="/tags/C4-5/" style="font-size:10px">C4.5</a> <a href="/tags/CART/" style="font-size:10px">CART</a> <a href="/tags/CD/" style="font-size:10px">CD</a> <a href="/tags/CLI/" style="font-size:10px">CLI</a> <a href="/tags/CSS/" style="font-size:10px">CSS</a> <a href="/tags/CentOS/" style="font-size:11.11px">CentOS</a> <a href="/tags/Cinder/" style="font-size:10px">Cinder</a> <a href="/tags/Clipboardjs/" style="font-size:10px">Clipboardjs</a> <a href="/tags/Concept/" style="font-size:10px">Concept</a> <a href="/tags/Continuous-Delivery/" style="font-size:10px">Continuous Delivery</a> <a href="/tags/DeepLearning/" style="font-size:16.67px">DeepLearning</a> <a href="/tags/Department/" style="font-size:11.11px">Department</a> <a href="/tags/DevOps/" style="font-size:17.78px">DevOps</a> <a href="/tags/Develop/" style="font-size:13.33px">Develop</a> <a href="/tags/Development/" style="font-size:20px">Development</a> <a href="/tags/Directive/" style="font-size:10px">Directive</a> <a href="/tags/Distribution/" style="font-size:10px">Distribution</a> <a href="/tags/Django/" style="font-size:12.22px">Django</a> <a href="/tags/Document/" style="font-size:10px">Document</a> <a href="/tags/ES5/" style="font-size:10px">ES5</a> <a href="/tags/ES6/" style="font-size:10px">ES6</a> <a href="/tags/Echarts/" style="font-size:10px">Echarts</a> <a href="/tags/Engine/" style="font-size:10px">Engine</a> <a href="/tags/Entropy/" style="font-size:10px">Entropy</a> <a href="/tags/Filter/" style="font-size:10px">Filter</a> <a href="/tags/Front-End/" style="font-size:10px">Front End</a> <a href="/tags/Gallery/" style="font-size:10px">Gallery</a> <a href="/tags/Git/" style="font-size:10px">Git</a> <a href="/tags/Gradient-descent/" style="font-size:10px">Gradient descent</a> <a href="/tags/Hexo/" style="font-size:12.22px">Hexo</a> <a href="/tags/Horizon/" style="font-size:10px">Horizon</a> <a href="/tags/ID3-5/" style="font-size:10px">ID3.5</a> <a href="/tags/Icarus/" style="font-size:10px">Icarus</a> <a href="/tags/JavaScript/" style="font-size:18.89px">JavaScript</a> <a href="/tags/Javascript/" style="font-size:11.11px">Javascript</a> <a href="/tags/KVM/" style="font-size:10px">KVM</a> <a href="/tags/LaTeX/" style="font-size:10px">LaTeX</a> <a href="/tags/LibreOffice/" style="font-size:10px">LibreOffice</a> <a href="/tags/Linux/" style="font-size:10px">Linux</a> <a href="/tags/Machine-Learning/" style="font-size:11.11px">Machine Learning</a> <a href="/tags/Matrix/" style="font-size:10px">Matrix</a> <a href="/tags/MiddleWare/" style="font-size:10px">MiddleWare</a> <a href="/tags/Module/" style="font-size:10px">Module</a> <a href="/tags/Network/" style="font-size:10px">Network</a> <a href="/tags/Nginx/" style="font-size:10px">Nginx</a> <a href="/tags/NodeJS/" style="font-size:11.11px">NodeJS</a> <a href="/tags/OOP/" style="font-size:10px">OOP</a> <a href="/tags/OpenStack/" style="font-size:17.78px">OpenStack</a> <a href="/tags/OpenStackApi/" style="font-size:14.44px">OpenStackApi</a> <a href="/tags/Operations/" style="font-size:10px">Operations</a> <a href="/tags/Oprations/" style="font-size:11.11px">Oprations</a> <a href="/tags/PDF/" style="font-size:10px">PDF</a> <a href="/tags/Pipline/" style="font-size:10px">Pipline</a> <a href="/tags/Probability/" style="font-size:12.22px">Probability</a> <a href="/tags/Python/" style="font-size:15.56px">Python</a> <a href="/tags/React/" style="font-size:11.11px">React</a> <a href="/tags/Relational-algebra/" style="font-size:10px">Relational algebra</a> <a href="/tags/Restful/" style="font-size:10px">Restful</a> <a href="/tags/Route/" style="font-size:10px">Route</a> <a href="/tags/SVD/" style="font-size:10px">SVD</a> <a href="/tags/SVM/" style="font-size:11.11px">SVM</a> <a href="/tags/Scalar/" style="font-size:10px">Scalar</a> <a href="/tags/Sigmoid/" style="font-size:10px">Sigmoid</a> <a href="/tags/Team/" style="font-size:10px">Team</a> <a href="/tags/Tempest/" style="font-size:10px">Tempest</a> <a href="/tags/Tensor/" style="font-size:10px">Tensor</a> <a href="/tags/Testing/" style="font-size:10px">Testing</a> <a href="/tags/Time/" style="font-size:10px">Time</a> <a href="/tags/TimeMachine/" style="font-size:10px">TimeMachine</a> <a href="/tags/Tips/" style="font-size:10px">Tips</a> <a href="/tags/Vector/" style="font-size:10px">Vector</a> <a href="/tags/Vmware/" style="font-size:10px">Vmware</a> <a href="/tags/Vue/" style="font-size:17.78px">Vue</a> <a href="/tags/Vuex/" style="font-size:10px">Vuex</a> <a href="/tags/WSGI/" style="font-size:10px">WSGI</a> <a href="/tags/Web/" style="font-size:10px">Web</a> <a href="/tags/Word-Cut/" style="font-size:10px">Word Cut</a> <a href="/tags/aliyun/" style="font-size:10px">aliyun</a> <a href="/tags/auth/" style="font-size:10px">auth</a> <a href="/tags/babel/" style="font-size:10px">babel</a> <a href="/tags/certbot/" style="font-size:10px">certbot</a> <a href="/tags/decision-tree/" style="font-size:10px">decision tree</a> <a href="/tags/homebrew/" style="font-size:10px">homebrew</a> <a href="/tags/jwt/" style="font-size:10px">jwt</a> <a href="/tags/keystone/" style="font-size:11.11px">keystone</a> <a href="/tags/loader/" style="font-size:10px">loader</a> <a href="/tags/mathematics/" style="font-size:11.11px">mathematics</a> <a href="/tags/migrate/" style="font-size:10px">migrate</a> <a href="/tags/openstack/" style="font-size:10px">openstack</a> <a href="/tags/pdf2html/" style="font-size:10px">pdf2html</a> <a href="/tags/pm2/" style="font-size:10px">pm2</a> <a href="/tags/singular-value-decomposition/" style="font-size:10px">singular value decomposition</a> <a href="/tags/vue-router/" style="font-size:11.11px">vue-router</a> <a href="/tags/vue-ssr/" style="font-size:10px">vue-ssr</a> <a href="/tags/webpack/" style="font-size:10px">webpack</a> <a href="/tags/%E4%BD%8E%E9%80%9A%E6%BB%A4%E6%B3%A2%E5%99%A8/" style="font-size:10px">低通滤波器</a> <a href="/tags/%E5%85%A5%E9%97%A8/" style="font-size:10px">入门</a> <a href="/tags/%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F/" style="font-size:10px">全局变量</a> <a href="/tags/%E5%85%A8%E5%B1%80%E5%AF%B9%E8%B1%A1/" style="font-size:10px">全局对象</a> <a href="/tags/%E5%85%A8%E6%A0%88/" style="font-size:10px">全栈</a> <a href="/tags/%E5%85%AC%E5%BC%8F/" style="font-size:12.22px">公式</a> <a href="/tags/%E5%86%B3%E7%AD%96%E6%A0%91/" style="font-size:10px">决策树</a> <a href="/tags/%E5%87%BD%E6%95%B0/" style="font-size:10px">函数</a> <a href="/tags/%E5%88%86%E7%B1%BB%E5%99%A8/" style="font-size:10px">分类器</a> <a href="/tags/%E5%8A%A0%E9%80%9F/" style="font-size:10px">加速</a> <a href="/tags/%E5%8C%B9%E9%85%8D%E6%BB%A4%E6%B3%A2%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/" style="font-size:10px">匹配滤波边缘检测</a> <a href="/tags/%E5%8D%B7%E7%A7%AF/" style="font-size:10px">卷积</a> <a href="/tags/%E5%8D%B7%E7%A7%AF%E6%A0%B8/" style="font-size:10px">卷积核</a> <a href="/tags/%E5%8E%9F%E5%9E%8B%E9%93%BE/" style="font-size:10px">原型链</a> <a href="/tags/%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD/" style="font-size:10px">反向传播</a> <a href="/tags/%E5%8F%98%E9%87%8F%E7%B1%BB%E5%9E%8B/" style="font-size:10px">变量类型</a> <a href="/tags/%E5%AE%98%E6%96%B9%E7%A4%BA%E4%BE%8B/" style="font-size:10px">官方示例</a> <a href="/tags/%E5%AF%B9%E5%81%B6%E5%BD%A2%E5%BC%8F/" style="font-size:10px">对偶形式</a> <a href="/tags/%E5%AF%B9%E8%B1%A1/" style="font-size:10px">对象</a> <a href="/tags/%E5%B9%B3%E7%A7%BB%E5%92%8C%E6%9F%A5%E5%88%86%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/" style="font-size:10px">平移和查分边缘检测</a> <a href="/tags/%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E4%B9%98%E5%AD%90%E6%B3%95/" style="font-size:10px">拉格朗日乘子法</a> <a href="/tags/%E6%8E%A8%E5%AF%BC/" style="font-size:12.22px">推导</a> <a href="/tags/%E6%8F%90%E4%BA%A4%E9%98%B6%E6%AE%B5/" style="font-size:10px">提交阶段</a> <a href="/tags/%E6%9C%80%E5%A4%A7%E4%BC%BC%E7%84%B6%E4%BC%B0%E8%AE%A1/" style="font-size:10px">最大似然估计</a> <a href="/tags/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E4%BC%B0%E8%AE%A1/" style="font-size:10px">最小二乘估计</a> <a href="/tags/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E6%B3%95/" style="font-size:10px">最小二乘法</a> <a href="/tags/%E6%9C%B4%E7%B4%A0%E8%B4%9D%E5%8F%B6%E6%96%AF/" style="font-size:10px">朴素贝叶斯</a> <a href="/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/" style="font-size:10px">机器学习</a> <a href="/tags/%E6%9D%A1%E4%BB%B6%E6%A6%82%E7%8E%87/" style="font-size:10px">条件概率</a> <a href="/tags/%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D/" style="font-size:11.11px">梯度下降</a> <a href="/tags/%E6%A2%AF%E5%BA%A6%E6%96%B9%E5%90%91%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/" style="font-size:10px">梯度方向边缘检测</a> <a href="/tags/%E6%A6%82%E5%BF%B5/" style="font-size:10px">概念</a> <a href="/tags/%E6%A6%82%E7%8E%87/" style="font-size:10px">概率</a> <a href="/tags/%E6%B1%82%E5%AF%BC/" style="font-size:10px">求导</a> <a href="/tags/%E6%B5%81%E7%A8%8B/" style="font-size:10px">流程</a> <a href="/tags/%E7%86%B5/" style="font-size:10px">熵</a> <a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:10px">生命周期</a> <a href="/tags/%E7%9F%A9%E9%98%B5/" style="font-size:10px">矩阵</a> <a href="/tags/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/" style="font-size:10px">神经网络</a> <a href="/tags/%E7%A7%81%E6%9C%89%E5%AF%B9%E8%B1%A1/" style="font-size:10px">私有对象</a> <a href="/tags/%E7%B1%BB/" style="font-size:10px">类</a> <a href="/tags/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/" style="font-size:10px">线性回归</a> <a href="/tags/%E8%81%94%E5%90%88%E6%A6%82%E7%8E%87/" style="font-size:10px">联合概率</a> <a href="/tags/%E8%B4%9D%E5%8F%B6%E6%96%AF%E5%88%A4%E5%AE%9A%E5%87%86%E5%88%99/" style="font-size:10px">贝叶斯判定准则</a> <a href="/tags/%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/" style="font-size:10px">边缘检测</a> <a href="/tags/%E8%BE%B9%E9%99%85%E6%A6%82%E7%8E%87/" style="font-size:10px">边际概率</a> <a href="/tags/%E9%97%AD%E5%8C%85/" style="font-size:10px">闭包</a> <a href="/tags/%E9%97%B4%E9%9A%94/" style="font-size:11.11px">间隔</a> <a href="/tags/%E9%AB%98%E6%96%AF%E5%88%86%E5%B8%83/" style="font-size:10px">高斯分布</a> <a href="/tags/%E9%AB%98%E9%80%9A%E6%BB%A4%E6%B3%A2%E5%99%A8/" style="font-size:10px">高通滤波器</a></div></div><div class="column-right-shadow"><div class="card widget"><div class="card-content"><h3 class="menu-label">最新文章</h3><article class="media"><a href="/2019-11-24/4adcf99c.html" class="media-left"><p class="image is-64x64"><img class="thumbnail" src="https://imgs.borgor.cn/imgs/20191124220507.png" alt="JavaScript闭包到底解决了什么问题？"></p></a><div class="media-content"><div class="content"><div><time class="has-text-grey is-size-7 is-uppercase" datetime="2019-11-24T12:42:13.000Z">2019-11-24</time></div><a href="/2019-11-24/4adcf99c.html" class="has-link-black-ter is-size-6">JavaScript闭包到底解决了什么问题？</a><p class="is-size-7 is-uppercase"><a class="has-link-grey -link" href="/categories/Development/">Development</a> / <a class="has-link-grey -link" href="/categories/Development/JavaScript/">JavaScript</a></p></div></div></article><article class="media"><a href="/2019-11-22/f971e187.html" class="media-left"><p class="image is-64x64"><img class="thumbnail" src="https://imgs.borgor.cn/imgs/20191122161153.png" alt="JavaScript中对象的生命周期"></p></a><div class="media-content"><div class="content"><div><time class="has-text-grey is-size-7 is-uppercase" datetime="2019-11-22T06:04:32.000Z">2019-11-22</time></div><a href="/2019-11-22/f971e187.html" class="has-link-black-ter is-size-6">JavaScript中对象的生命周期</a><p class="is-size-7 is-uppercase"><a class="has-link-grey -link" href="/categories/Development/">Development</a> / <a class="has-link-grey -link" href="/categories/Development/JavaScript/">JavaScript</a></p></div></div></article><article class="media"><a href="/2019-11-21/c07cc943.html" class="media-left"><p class="image is-64x64"><img class="thumbnail" src="https://imgs.borgor.cn/imgs/20191121175758.png" alt="React官方示例中的小游戏示例"></p></a><div class="media-content"><div class="content"><div><time class="has-text-grey is-size-7 is-uppercase" datetime="2019-11-21T09:56:03.000Z">2019-11-21</time></div><a href="/2019-11-21/c07cc943.html" class="has-link-black-ter is-size-6">React官方示例中的小游戏示例</a><p class="is-size-7 is-uppercase"><a class="has-link-grey -link" href="/categories/Development/">Development</a> / <a class="has-link-grey -link" href="/categories/Development/React/">React</a></p></div></div></article><article class="media"><a href="/2019-11-21/769edcc6.html" class="media-left"><p class="image is-64x64"><img class="thumbnail" src="https://imgs.borgor.cn/imgs/20191121141128.png" alt="JavaScript引擎的工作原理探析"></p></a><div class="media-content"><div class="content"><div><time class="has-text-grey is-size-7 is-uppercase" datetime="2019-11-21T00:30:12.000Z">2019-11-21</time></div><a href="/2019-11-21/769edcc6.html" class="has-link-black-ter is-size-6">JavaScript引擎的工作原理探析</a><p class="is-size-7 is-uppercase"><a class="has-link-grey -link" href="/categories/Development/">Development</a> / <a class="has-link-grey -link" href="/categories/Development/JavaScript/">JavaScript</a></p></div></div></article><article class="media"><a href="/2019-11-19/290f6390.html" class="media-left"><p class="image is-64x64"><img class="thumbnail" src="https://imgs.borgor.cn/imgs/20191119100237.png" alt="深入了解JavaScript中的基本变量类型"></p></a><div class="media-content"><div class="content"><div><time class="has-text-grey is-size-7 is-uppercase" datetime="2019-11-19T04:22:33.000Z">2019-11-19</time></div><a href="/2019-11-19/290f6390.html" class="has-link-black-ter is-size-6">深入了解JavaScript中的基本变量类型</a><p class="is-size-7 is-uppercase"><a class="has-link-grey -link" href="/categories/Development/">Development</a> / <a class="has-link-grey -link" href="/categories/Development/JavaScript/">JavaScript</a></p></div></div></article></div></div><div class="card widget"><div class="card-content"><div class="menu"><h3 class="menu-label">归档</h3><ul class="menu-list"><li><a class="level is-marginless" href="/archives/2019/11/"><span class="level-start"><span class="level-item">十一月 2019</span> </span><span class="level-end"><span class="level-item tag">7</span></span></a></li><li><a class="level is-marginless" href="/archives/2019/10/"><span class="level-start"><span class="level-item">十月 2019</span> </span><span class="level-end"><span class="level-item tag">8</span></span></a></li><li><a class="level is-marginless" href="/archives/2019/09/"><span class="level-start"><span class="level-item">九月 2019</span> </span><span class="level-end"><span class="level-item tag">5</span></span></a></li><li><a class="level is-marginless" href="/archives/2019/08/"><span class="level-start"><span class="level-item">八月 2019</span> </span><span class="level-end"><span class="level-item tag">13</span></span></a></li><li><a class="level is-marginless" href="/archives/2019/07/"><span class="level-start"><span class="level-item">七月 2019</span> </span><span class="level-end"><span class="level-item tag">15</span></span></a></li><li><a class="level is-marginless" href="/archives/2019/06/"><span class="level-start"><span class="level-item">六月 2019</span> </span><span class="level-end"><span class="level-item tag">11</span></span></a></li><li><a class="level is-marginless" href="/archives/2018/07/"><span class="level-start"><span class="level-item">七月 2018</span> </span><span class="level-end"><span class="level-item tag">4</span></span></a></li><li><a class="level is-marginless" href="/archives/2018/06/"><span class="level-start"><span class="level-item">六月 2018</span> </span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-marginless" href="/archives/2017/12/"><span class="level-start"><span class="level-item">十二月 2017</span> </span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-marginless" href="/archives/2017/11/"><span class="level-start"><span class="level-item">十一月 2017</span> </span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-marginless" href="/archives/2017/10/"><span class="level-start"><span class="level-item">十月 2017</span> </span><span class="level-end"><span class="level-item tag">5</span></span></a></li><li><a class="level is-marginless" href="/archives/2017/09/"><span class="level-start"><span class="level-item">九月 2017</span> </span><span class="level-end"><span class="level-item tag">10</span></span></a></li></ul></div></div></div><div class="card widget"><div class="card-content"><div class="menu"><h3 class="menu-label">标签</h3><div class="field is-grouped is-grouped-multiline"><div class="control"><a class="tags has-addons" href="/tags/Agile/"><span class="tag">Agile</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Angularjs/"><span class="tag">Angularjs</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Animation/"><span class="tag">Animation</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Application/"><span class="tag">Application</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Artificial-Intelligence/"><span class="tag">Artificial Intelligence</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/BP/"><span class="tag">BP</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Bokeh/"><span class="tag">Bokeh</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Book/"><span class="tag">Book</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/C4-5/"><span class="tag">C4.5</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/CART/"><span class="tag">CART</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/CD/"><span class="tag">CD</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/CLI/"><span class="tag">CLI</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/CSS/"><span class="tag">CSS</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/CentOS/"><span class="tag">CentOS</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Cinder/"><span class="tag">Cinder</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Clipboardjs/"><span class="tag">Clipboardjs</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Concept/"><span class="tag">Concept</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Continuous-Delivery/"><span class="tag">Continuous Delivery</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/DeepLearning/"><span class="tag">DeepLearning</span> <span class="tag is-grey">7</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Department/"><span class="tag">Department</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/DevOps/"><span class="tag">DevOps</span> <span class="tag is-grey">8</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Develop/"><span class="tag">Develop</span> <span class="tag is-grey">4</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Development/"><span class="tag">Development</span> <span class="tag is-grey">16</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Directive/"><span class="tag">Directive</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Distribution/"><span class="tag">Distribution</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Django/"><span class="tag">Django</span> <span class="tag is-grey">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Document/"><span class="tag">Document</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/ES5/"><span class="tag">ES5</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/ES6/"><span class="tag">ES6</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Echarts/"><span class="tag">Echarts</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Engine/"><span class="tag">Engine</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Entropy/"><span class="tag">Entropy</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Filter/"><span class="tag">Filter</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Front-End/"><span class="tag">Front End</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Gallery/"><span class="tag">Gallery</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Git/"><span class="tag">Git</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Gradient-descent/"><span class="tag">Gradient descent</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Hexo/"><span class="tag">Hexo</span> <span class="tag is-grey">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Horizon/"><span class="tag">Horizon</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/ID3-5/"><span class="tag">ID3.5</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Icarus/"><span class="tag">Icarus</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/JavaScript/"><span class="tag">JavaScript</span> <span class="tag is-grey">9</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Javascript/"><span class="tag">Javascript</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/KVM/"><span class="tag">KVM</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/LaTeX/"><span class="tag">LaTeX</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/LibreOffice/"><span class="tag">LibreOffice</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Linux/"><span class="tag">Linux</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Machine-Learning/"><span class="tag">Machine Learning</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Matrix/"><span class="tag">Matrix</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/MiddleWare/"><span class="tag">MiddleWare</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Module/"><span class="tag">Module</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Network/"><span class="tag">Network</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Nginx/"><span class="tag">Nginx</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/NodeJS/"><span class="tag">NodeJS</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/OOP/"><span class="tag">OOP</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/OpenStack/"><span class="tag">OpenStack</span> <span class="tag is-grey">8</span></a></div><div class="control"><a class="tags has-addons" href="/tags/OpenStackApi/"><span class="tag">OpenStackApi</span> <span class="tag is-grey">5</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Operations/"><span class="tag">Operations</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Oprations/"><span class="tag">Oprations</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/PDF/"><span class="tag">PDF</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Pipline/"><span class="tag">Pipline</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Probability/"><span class="tag">Probability</span> <span class="tag is-grey">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Python/"><span class="tag">Python</span> <span class="tag is-grey">6</span></a></div><div class="control"><a class="tags has-addons" href="/tags/React/"><span class="tag">React</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Relational-algebra/"><span class="tag">Relational algebra</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Restful/"><span class="tag">Restful</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Route/"><span class="tag">Route</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/SVD/"><span class="tag">SVD</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/SVM/"><span class="tag">SVM</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Scalar/"><span class="tag">Scalar</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Sigmoid/"><span class="tag">Sigmoid</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Team/"><span class="tag">Team</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Tempest/"><span class="tag">Tempest</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Tensor/"><span class="tag">Tensor</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Testing/"><span class="tag">Testing</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Time/"><span class="tag">Time</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/TimeMachine/"><span class="tag">TimeMachine</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Tips/"><span class="tag">Tips</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Vector/"><span class="tag">Vector</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Vmware/"><span class="tag">Vmware</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Vue/"><span class="tag">Vue</span> <span class="tag is-grey">8</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Vuex/"><span class="tag">Vuex</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/WSGI/"><span class="tag">WSGI</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Web/"><span class="tag">Web</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/Word-Cut/"><span class="tag">Word Cut</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/aliyun/"><span class="tag">aliyun</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/auth/"><span class="tag">auth</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/babel/"><span class="tag">babel</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/certbot/"><span class="tag">certbot</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/decision-tree/"><span class="tag">decision tree</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/homebrew/"><span class="tag">homebrew</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/jwt/"><span class="tag">jwt</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/keystone/"><span class="tag">keystone</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/loader/"><span class="tag">loader</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/mathematics/"><span class="tag">mathematics</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/migrate/"><span class="tag">migrate</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/openstack/"><span class="tag">openstack</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/pdf2html/"><span class="tag">pdf2html</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/pm2/"><span class="tag">pm2</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/singular-value-decomposition/"><span class="tag">singular value decomposition</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/vue-router/"><span class="tag">vue-router</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/vue-ssr/"><span class="tag">vue-ssr</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/webpack/"><span class="tag">webpack</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E4%BD%8E%E9%80%9A%E6%BB%A4%E6%B3%A2%E5%99%A8/"><span class="tag">低通滤波器</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%85%A5%E9%97%A8/"><span class="tag">入门</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F/"><span class="tag">全局变量</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%85%A8%E5%B1%80%E5%AF%B9%E8%B1%A1/"><span class="tag">全局对象</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%85%A8%E6%A0%88/"><span class="tag">全栈</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%85%AC%E5%BC%8F/"><span class="tag">公式</span> <span class="tag is-grey">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%86%B3%E7%AD%96%E6%A0%91/"><span class="tag">决策树</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%87%BD%E6%95%B0/"><span class="tag">函数</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%88%86%E7%B1%BB%E5%99%A8/"><span class="tag">分类器</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%8A%A0%E9%80%9F/"><span class="tag">加速</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%8C%B9%E9%85%8D%E6%BB%A4%E6%B3%A2%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/"><span class="tag">匹配滤波边缘检测</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%8D%B7%E7%A7%AF/"><span class="tag">卷积</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%8D%B7%E7%A7%AF%E6%A0%B8/"><span class="tag">卷积核</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%8E%9F%E5%9E%8B%E9%93%BE/"><span class="tag">原型链</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD/"><span class="tag">反向传播</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%8F%98%E9%87%8F%E7%B1%BB%E5%9E%8B/"><span class="tag">变量类型</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%AE%98%E6%96%B9%E7%A4%BA%E4%BE%8B/"><span class="tag">官方示例</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%AF%B9%E5%81%B6%E5%BD%A2%E5%BC%8F/"><span class="tag">对偶形式</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%AF%B9%E8%B1%A1/"><span class="tag">对象</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E5%B9%B3%E7%A7%BB%E5%92%8C%E6%9F%A5%E5%88%86%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/"><span class="tag">平移和查分边缘检测</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E4%B9%98%E5%AD%90%E6%B3%95/"><span class="tag">拉格朗日乘子法</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%8E%A8%E5%AF%BC/"><span class="tag">推导</span> <span class="tag is-grey">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%8F%90%E4%BA%A4%E9%98%B6%E6%AE%B5/"><span class="tag">提交阶段</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%9C%80%E5%A4%A7%E4%BC%BC%E7%84%B6%E4%BC%B0%E8%AE%A1/"><span class="tag">最大似然估计</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E4%BC%B0%E8%AE%A1/"><span class="tag">最小二乘估计</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E6%B3%95/"><span class="tag">最小二乘法</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%9C%B4%E7%B4%A0%E8%B4%9D%E5%8F%B6%E6%96%AF/"><span class="tag">朴素贝叶斯</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"><span class="tag">机器学习</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%9D%A1%E4%BB%B6%E6%A6%82%E7%8E%87/"><span class="tag">条件概率</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D/"><span class="tag">梯度下降</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%A2%AF%E5%BA%A6%E6%96%B9%E5%90%91%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/"><span class="tag">梯度方向边缘检测</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%A6%82%E5%BF%B5/"><span class="tag">概念</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%A6%82%E7%8E%87/"><span class="tag">概率</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%B1%82%E5%AF%BC/"><span class="tag">求导</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E6%B5%81%E7%A8%8B/"><span class="tag">流程</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%86%B5/"><span class="tag">熵</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/"><span class="tag">生命周期</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%9F%A9%E9%98%B5/"><span class="tag">矩阵</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/"><span class="tag">神经网络</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%A7%81%E6%9C%89%E5%AF%B9%E8%B1%A1/"><span class="tag">私有对象</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%B1%BB/"><span class="tag">类</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92/"><span class="tag">线性回归</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E8%81%94%E5%90%88%E6%A6%82%E7%8E%87/"><span class="tag">联合概率</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E8%B4%9D%E5%8F%B6%E6%96%AF%E5%88%A4%E5%AE%9A%E5%87%86%E5%88%99/"><span class="tag">贝叶斯判定准则</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B/"><span class="tag">边缘检测</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E8%BE%B9%E9%99%85%E6%A6%82%E7%8E%87/"><span class="tag">边际概率</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E9%97%AD%E5%8C%85/"><span class="tag">闭包</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E9%97%B4%E9%9A%94/"><span class="tag">间隔</span> <span class="tag is-grey">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E9%AB%98%E6%96%AF%E5%88%86%E5%B8%83/"><span class="tag">高斯分布</span> <span class="tag is-grey">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%E9%AB%98%E9%80%9A%E6%BB%A4%E6%B3%A2%E5%99%A8/"><span class="tag">高通滤波器</span> <span class="tag is-grey">1</span></a></div></div></div></div></div></div></div></div></div></section><footer class="footer"><div class="container"><div class="level"><div class="level-start has-text-centered-mobile"><a class="footer-logo is-block has-mb-6" href="/"><img src="https://imgs.borgor.cn/imgs20190625111723.jpg" alt="Vue-SSR(Server-Side-Renderer)服务器端渲染指南" height="28"></a><p class="is-size-7">&copy; 2017-2019 Cyrusky&nbsp; Powered by <a href="https://hexo.io/" target="_blank">Hexo</a> & <a href="https://github.com/ppoffice/hexo-theme-icarus" target="_blank">Icarus</a><br><span id="busuanzi_container_site_uv">共<span id="busuanzi_value_site_uv">0</span>个访客</span><br><a target="_blank" href="https://icp.chinaz.com/info?q=borgor.cn">陇ICP备19002255号</a></p></div><div class="level-end"><div class="field has-addons is-flex-center-mobile has-mt-5-mobile is-flex-wrap is-flex-middle"><p class="control"><a class="button is-white is-large" target="_blank" title="Creative Commons" href="https://creativecommons.org/"><i class="fab fa-creative-commons"></i></a></p><p class="control"><a class="button is-white is-large" target="_blank" title="Attribution 4.0 International" href="https://creativecommons.org/licenses/by/4.0/"><i class="fab fa-creative-commons-by"></i></a></p><p class="control"><a class="button is-white is-large" target="_blank" title="Download on GitHub" href="https://github.com/ppoffice/hexo-theme-icarus"><i class="fab fa-github"></i></a></p></div></div></div></div></footer><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment-with-locales.min.js"></script><script>moment.locale("zh-CN")</script><script src="/js/animation.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/1.6.8/js/lightgallery.min.js" defer></script><script src="https://cdnjs.cloudflare.com/ajax/libs/justifiedGallery/3.7.0/js/jquery.justifiedGallery.min.js" defer></script><script src="/js/gallery.js" defer></script><div id="outdated"><h6>Your browser is out-of-date!</h6><p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" href="http://outdatedbrowser.com/" target="_blank" rel="noopener">Update my browser now</a></p><p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p></div><script src="https://cdnjs.cloudflare.com/ajax/libs/outdated-browser/1.1.5/outdatedbrowser.min.js" defer></script><script>document.addEventListener("DOMContentLoaded",function(){outdatedBrowser({bgColor:"#f25648",color:"#ffffff",lowerThan:"flex"})})</script><script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML" defer></script><script>document.addEventListener("DOMContentLoaded",function(){MathJax.Hub.Config({"HTML-CSS":{matchFontHeight:!1},SVG:{matchFontHeight:!1},CommonHTML:{matchFontHeight:!1},tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]]}})})</script><a id="back-to-top" title="回到顶端" href="javascript:;" target="_blank" rel="noopener"><i class="fas fa-chevron-up"></i></a><script src="/js/back-to-top.js" defer></script><script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js" defer></script><script src="/js/clipboard.js" defer></script><script src="/js/main.js" defer></script><div class="searchbox ins-search"><div class="searchbox-container ins-search-container"><div class="searchbox-input-wrapper"><input type="text" class="searchbox-input ins-search-input" placeholder="想要查找什么..."> <span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span></div><div class="searchbox-result-wrapper ins-section-wrapper"><div class="ins-section-container"></div></div></div></div><script>!function(T){var N={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(无标题)"},CONTENT_URL:"/content.json"};T.INSIGHT_CONFIG=N}(window)</script><script src="/js/insight.js" defer></script><link rel="stylesheet" href="/css/search.css"><link rel="stylesheet" href="/css/insight.css"></body></html><!-- rebuild by neat -->